本手册(2021 年 9 月 10 日)适用于 GNU Bison(版本 3.8.1), GNU 解析器生成器。
版权所有 © 1988–1993、1995、1998–2015、2018–2021 免费 软件基金会
授予复制、分发和/或修改本文档的权限 GNU 自由文档许可证 1.3 版或更高版本的条款 由自由软件基金会发布的版本;没有不变性 部分,封面文本为“GNU 手册”,以及 封底文字如下(a)所示。许可证的副本包含在 标题为“GNU 自由文档许可证”的部分。
(a) 自由软件基金会的封底文字是:“您可以自由复制和修改 这本 GNU 手册。从 FSF 购买副本支持它开发 GNU 并促进软件自由。
Bison 是一个通用的解析器生成器,用于转换带注释的 将上下文无关语法转换为确定性 LR 或广义 LR (GLR) 解析器 使用 LALR(1)、IELR(1) 或规范的 LR(1) 解析器表。一旦你是 精通野牛,你可以用它来发展广泛的语言 解析器,从简单的桌面计算器到复杂编程中使用的解析器 语言。
Bison 向上兼容 Yacc:所有正确编写的 Yacc 语法 应该与野牛一起工作,没有任何变化。任何熟悉 Yacc 的人都应该 能够毫不费力地使用 Bison。您需要精通 C、C++、 D或Java编程,以便使用Bison或理解本手册。
我们从教程章节开始,解释 使用 Bison 并显示三个解释示例,每个构建在 最后。如果您不了解 Bison 或 Yacc,请从阅读这些开始 章。以下是描述具体方面的参考章节 野牛的细节。
《野牛》最初由罗伯特·科贝特(Robert Corbett)撰写。理查德·斯托曼(Richard Stallman)制作 它与 Yacc 兼容。卡内基梅隆大学的威尔弗雷德·汉森(Wilfred Hansen) 添加了多字符字符串文本和其他功能。从那时起, Bison 变得更加强大并发展了许多其他新功能,这要归功于 感谢一长串志愿者的辛勤工作。有关详细信息,请参阅 Bison 中包含的 和 文件 分配。THANKSChangeLog
此版本对应于 Bison 的 3.8.1 版本。
下一篇: GNU通用公共许可证, 上一篇: 简介, 上一篇: 野牛 [内容][索引]
Bison 生成的解析器的分发条款允许使用解析器 在非自由程序中。在 Bison 版本 2.2 之前,这些额外权限 仅当 Bison 在 C 语言中生成 LALR(1) 解析器时才适用。和之前 Bison 版本 1.24,Bison 生成的解析器只能在程序中使用 那是自由软件。
其他 GNU 编程工具,如 GNU C 编译器,从未有过 这样的要求。它们总是可以用于非自由软件。这 野牛与众不同的原因不是由于特殊的政策决定;它 由于将通常的通用公共许可证应用于所有野牛 源代码。
Bison 实用程序的主要输出 — Bison 解析器实现 文件 - 包含一大块野牛的逐字副本,即 解析器实现的代码。(语法中的操作是 在某一时刻插入到此实现中,但其余大部分 实现不会更改。当我们将 GPL 术语应用于 骨架代码的实现,效果是限制 使用 Bison 输出到自由软件。
我们没有改变条款,因为对那些想要制作的人表示同情 软件专有。软件应该是自由的。但我们得出结论 将 Bison 的使用限制在自由软件上并没有起到什么作用 人们使其他软件免费。因此,我们决定将 使用Bison的条件与使用Bison的实际条件相匹配。 其他 GNU 工具。
当 Bison 为解析器生成代码时,此例外适用。您可以 通过检查 以“作为特殊例外...”开头的文本的文件。文本 详细说明例外的确切条款。
Copyright © 2007 Free Software Foundation, Inc. https://fsf.org/ Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
GNU 通用公共许可证是一个免费的 copyleft 许可证,用于 软件和其他类型的作品。
大多数软件和其他实际作品的许可证都是设计的 剥夺你分享和改变作品的自由。相比之下, GNU通用公共许可证旨在保证您的自由 共享和更改程序的所有版本,以确保它保持不变 为所有用户提供免费软件。我们,自由软件基金会, 我们的大多数软件都使用 GNU 通用公共许可证;它 也适用于其作者以这种方式发布的任何其他作品。你 也可以将其应用于您的程序。
当我们谈论自由软件时,我们指的是自由,而不是 价格。我们的通用公共许可证旨在确保您 可以自由地分发自由软件的副本(并收取费用 如果您愿意,他们会收到源代码,或者如果您可以获得源代码 想要它,您可以更改软件或在新的软件中使用它的一部分 免费程序,并且你知道你可以做这些事情。
为了保护您的权利,我们需要防止他人拒绝您 这些权利或要求您放弃这些权利。因此,您 如果您分发 软件,或者如果您修改它:尊重自由的责任 其他人。
例如,如果您分发此类程序的副本,则是否 免费或收费,您必须将相同的内容传递给收件人 你所获得的自由。你必须确保他们也 接收或可以获取源代码。你必须向他们展示这些 条款,以便他们了解自己的权利。
使用 GNU GPL 的开发人员通过两个步骤来保护您的权利: (1) 主张软件的版权,以及 (2) 向您提供本许可 授予您复制、分发和/或修改其法律许可。
为了保护开发者和作者,GPL 清楚地解释了 此免费软件不提供任何保证。对于用户和 为了作者的缘故,GPL 要求修改后的版本标记为 改变,这样他们的问题就不会被错误地归咎于 以前版本的作者。
某些设备旨在拒绝用户访问安装或运行 其中软件的修改版本,尽管 制造商可以这样做。这从根本上与 旨在保护用户更改软件的自由。这 这种滥用的系统模式发生在产品领域 个人使用,这正是最不可接受的地方。 因此,我们设计了这个版本的 GPL 来禁止 练习这些产品。如果此类问题在 其他领域,我们随时准备将这一规定扩展到那些 域,以保护 用户的自由。
最后,每个程序都不断受到软件专利的威胁。 国家不应允许专利限制开发和使用 通用计算机上的软件,但在那些这样做的计算机上,我们希望 避免专利适用于自由程序的特殊危险 可以使它有效地专有化。为了防止这种情况,GPL 确保专利不能用于使程序成为非自由的。
复制、分发和 修改如下。
“本许可证”是指 GNU 通用公共许可证的第 3 版。
“版权”也指适用于其他种类的类似版权的法律 的作品,例如半导体掩模。
“本程序”是指根据本协议获得许可的任何受版权保护的作品 许可证。每个被许可人都称呼为“您”。“被许可人”和 “接收者”可以是个人或组织。
“修改”作品是指复制或改编全部或部分作品 以需要版权许可的方式,而不是制作 一模一样的副本。由此产生的作品称为 早期作品或“基于”早期作品的作品。
“涵盖的作品”是指未经修改的程序或基于作品的作品 在程序上。
“传播”一部作品意味着用它做任何事情,没有 许可,将使您直接或次要承担以下责任 根据适用的版权法侵权,除非在 计算机或修改私人副本。传播包括复制、 分发(有或没有修改),提供给 公共活动,在一些国家还有其他活动。
“传达”作品是指任何一种传播,使其他作品成为可能 制作或接收副本的当事人。仅仅与用户互动 通过计算机网络,没有传输副本,不是 输送。
交互式用户界面显示“适当的法律声明” 它包括方便和突出可见的程度 (1) 显示适当的版权声明的功能,以及 (2) 告诉用户对工作没有保证(除了 在提供保证的范围内),被许可人可以传达 在本许可证下工作,以及如何查看本许可证的副本。如果 该界面显示用户命令或选项的列表,例如 菜单,列表中的突出项目符合此标准。
作品的“源代码”是指作品的首选形式 对其进行修改。“目标代码”是指任何非源形式 的作品。
“标准接口”是指官方接口 由公认的标准机构定义的标准,或者,在以下情况下 为特定编程语言指定的接口,一种 在使用该语言的开发人员中广泛使用。
可执行作品的“系统库”包括任何内容,其他 比整个作品,即 (a) 包含在正常形式中 打包一个主要组件,但不属于该主要组件 组件,并且 (b) 仅用于使工作能够使用该组件 主要组件,或实现标准接口,其 实现以源代码形式向公众开放。一个 在这种情况下,“主要组成部分”是指一个主要的基本组成部分 (内核、窗口系统等)的特定操作系统 (如果有)运行可执行工作,或用于 生成工作,或用于运行它的目标代码解释器。
目标代码形式作品的“对应来源”是指所有 生成、安装和 (对于可执行文件) 所需的源代码 work)运行目标代码并修改工作,包括脚本 控制这些活动。但是,它不包括作品的 系统库,或通用工具,或正式发布的免费工具 在执行这些活动时未修改的程序,但 这些都不是工作的一部分。例如,对应源 包括与源文件关联的接口定义文件 共享库的工作和源代码,并动态地 工作专门设计为需要的链接子程序, 例如通过亲密的数据通信或控制流之间的那些 子程序和工作的其他部分。
相应的来源不需要包含用户可以包含的任何内容 从相应源的其他部分自动再生。
源代码形式作品的相应来源是相同的 工作。
根据本许可授予的所有权利均在以下期限内授予 程序的版权,并且不可撤销,前提是声明 满足条件。本许可明确确认您的无限 运行未修改程序的权限。运行 所涵盖的工作仅受本许可证的保护,前提是其 内容,构成涵盖作品。本许可证承认您的 版权法规定的合理使用权或其他等效权利。
您可以制作、运行和传播您不传达的涵盖作品, 无条件,只要您的许可证仍然有效。 您可以将涵盖的作品转让给他人,其唯一目的是拥有 他们专门为您进行修改,或为您提供 运行这些工程的设施,前提是您遵守 本许可的条款,用于传达您不这样做的所有材料 控制版权。因此制作或运行所涵盖作品的人 您必须完全代表您,在您的指导下这样做,并且 控制,禁止他们复制您的任何副本 他们与您的关系之外的受版权保护的材料。
在任何其他情况下,仅在 条件如下。不允许再许可;第10节 使它变得没有必要。
任何涵盖的工作均不应被视为有效技术的一部分 根据任何适用法律采取的措施,履行第条规定的义务 1996年12月20日通过的《世界知识产权组织版权条约》第11条,或 禁止或限制规避此类行为的类似法律 措施。
当您转让涵盖的作品时,您放弃任何禁止的法律权力 规避技术措施的程度 通过行使本许可项下的权利来实现规避 尊重所涵盖的工作,并且您否认有任何限制的意图 作为强制执行的手段对作品的操作或修改,反对 作品的使用者、您或第三方的合法禁止权 规避技术措施。
您可以作为您一字不差地传达本程序源代码的副本 在任何媒介上接收它,前提是您醒目地和 在每份副本上适当发布适当的版权声明; 保持所有声明完好无损的通知,说明本许可证和任何 根据第7条增加的非许可条款适用于本守则; 保持所有关于没有任何保证的通知的完整性;并全力以赴 接收本许可证的副本以及程序。
您可以对您传达的每份副本收取任何价格或无价格, 您可以付费提供支持或保修保护。
您可以根据本程序或对 从程序中生成它,以源代码的形式在 第 4 节的条款,前提是您还满足所有这些条件 条件:
涵盖作品与其他独立和独立作品的汇编 作品,就其性质而言不是所涵盖作品的延伸, 并且不与之结合,例如形成一个更大的程序, 在存储或分发介质的体积中或卷上,称为 “聚合”,如果汇编及其产生的版权不是 用于限制编译用户的访问或合法权限 超出个人工作允许的范围。纳入涵盖的作品 在总体上不会导致此许可证适用于其他许可证 骨料的一部分。
您可以根据以下条款以目标代码形式传达涵盖的作品 第 4 节和第 5 节,前提是您还传达了机器可读的内容 根据本许可条款的相应来源,在以下之一中 方式:
目标代码的可分离部分,其源代码被排除在外 从相应的源作为系统库,不需要 包含在传达目标代码工作中。
“用户产品”是 (1) “消费品”,即任何 有形个人财产,通常用于个人、 家庭或家庭用途,或 (2) 任何设计或销售的物品 并入住宅。在确定产品是否是 消费品,疑问案件应以有利于 覆盖。对于特定用户收到的特定产品, “通常使用”是指该类的典型或常见用途 产品,无论特定用户的状态或方式如何 特定用户实际使用、期望或预期 使用,产品。无论产品是消费品 产品是否具有实质性的商业、工业或 非消费者用途,除非此类用途是唯一重要的用途 产品的使用方式。
用户产品的“安装信息”是指任何方法, 过程、授权密钥或所需的其他信息 在该用户中安装并执行涵盖作品的修改版本 产品来自其相应来源的修改版本。这 信息必须足以确保 修改后的目标代码在任何情况下都不会被阻止或干扰 仅仅因为进行了修改。
如果您传达目标代码,请在本节中、或与或 专门用于用户产品,并且输送发生为 交易的一部分,其中占有和使用权 用户产品永久转让给收件人或转让给收件人 固定期限(无论交易的特征如何),, 根据本节传达的相应来源必须随附 通过安装信息。但此要求不适用 如果您或任何第三方均不保留安装能力 修改了用户产品上的目标代码(例如,作品具有 已安装在ROM中)。
提供安装信息的要求不包括 要求继续提供支持服务、保修或 已修改或安装的作品的更新 接收者,或已修改的用户产品,或 安装。修改时,可能会拒绝对网络的访问 本身对网络的运行产生重大不利影响 或违反跨 网络。
传达的相应来源,并提供安装信息, 根据本节的规定,必须采用公开的格式 记录在案(并在 源代码形式),并且必须不需要特殊的密码或密钥 开箱、阅读或复印。
“附加权限”是补充本条款的条款 通过对一个或多个条件进行例外处理来获得许可。 适用于整个程序的其他权限应 在一定程度上,应被视为包含在本许可中 它们在适用法律下有效。如果其他权限 仅适用于本程序的一部分,该部分可以单独使用 在这些权限下,但整个程序仍受 本许可证不考虑其他权限。
当您转交涵盖作品的副本时,您可以自行选择 从该副本或 它。(可以编写其他权限以要求自己的权限 在某些情况下,当您修改作品时,请将其删除。您可以放置 您添加到涵盖作品中的材料的其他权限, 您拥有或可以给予适当的版权许可。
尽管本许可有任何其他规定,但对于材料,您 添加到涵盖的作品中,您可以(如果获得版权所有者的授权) 该材料)用以下条款补充本许可的条款:
所有其他非允许的附加条款均被视为“进一步 限制“,符合第10条的含义。如果程序作为您 收到它或其任何部分包含通知,说明它是 受本许可证以及进一步的条款的约束 限制,您可以删除该术语。如果许可证文档包含 进一步的限制,但允许在此下重新许可或转让 许可,您可以添加到受条款约束的涵盖工作材料中 该许可文件,前提是进一步的限制确实 在此类重新许可或转让后无法幸存。
如果您根据本节的规定为涵盖的作品添加条款,则您 必须在相关的源文件中放置 适用于这些文件的附加条款,或指示 在哪里可以找到适用的条款。
其他条款,无论是允许的还是非允许的,都可以在 单独书面许可的形式,或声明为例外;这 上述要求适用于任何一种方式。
除非明确规定,否则您不得传播或修改涵盖的作品 根据本许可证提供。任何以其他方式传播或 修改它是无效的,并将自动终止您在以下权利 本许可(包括根据第三条授予的任何专利许可 第11条)。
但是,如果您停止所有违反本许可证的行为,则您的许可证 (a)暂时恢复特定版权所有者的版权, 除非并且直到版权所有者明确并最终 终止您的许可,并且 (b) 永久终止,如果版权所有者 未能在以下情况下通过某种合理方式通知您违规行为 戒烟后 60 天。
此外,您从特定版权所有者处获得的许可是 如果版权所有者通知您 通过一些合理的手段违规,这是你第一次有 收到违反本许可(任何作品)的通知 版权所有者,并且您在 30 天后纠正违规行为 您收到通知。
终止您在本节项下的权利并不终止 根据 本许可证。如果您的权利已被终止,而不是永久终止 恢复后,您没有资格获得相同的新许可证 第 10 节下的材料。
您无需接受此许可证即可接收或运行 程序的副本。涵盖作品的辅助传播 仅因使用点对点传输而发生 同样,接收副本也不需要接受。然而 除本许可证外,其他任何内容均授予您传播或 修改任何涵盖的工作。如果您这样做,这些行为将侵犯版权 不接受此许可证。因此,通过修改或传播 涵盖的工作,您表示您接受本许可。
每次您传达涵盖的作品时,收件人都会自动 从原始许可方获得许可证,以运行、修改和 传播该作品,但须遵守本许可证。您不承担责任 用于强制第三方遵守本许可证。
“实体交易”是指转让对 组织,或实质上所有资产,或细分一个 组织或合并组织。如果传播覆盖 工作源于实体交易,每一方都参与其中 收到作品副本的交易也会收到任何东西 当事人前任所涉作品的许可 根据前款给予,加上占有 感兴趣的前任的相应作品来源,如果 前任拥有它或可以通过合理的努力获得它。
您不得对行使 根据本许可授予或确认的权利。例如,您可以 不对行使 根据本许可授予的权利,您不得提起诉讼 (包括诉讼中的交叉申索或反申索)指称 任何专利权利要求均因制造、使用、销售、提供而受到侵犯 出售或导入程序或其任何部分。
“贡献者”是授权根据本协议使用的版权所有者 程序或程序所基于的作品的许可。这 这样许可的作品称为贡献者的“贡献者版本”。
贡献者的“基本专利权利要求”是所有拥有的专利权利要求 或由贡献者控制,无论是已经获得的还是 此后获得的,将以某种方式被侵犯,允许 根据本许可,制作、使用或销售其贡献者版本, 但不包括仅作为 进一步修改贡献者版本的结果。为 就本定义而言,“控制”包括授予的权利 以符合以下要求的方式进行专利再许可 本许可证。
每个贡献者都授予您非排他性、全球性、免版税 根据贡献者的基本专利权利要求,专利许可,以 制造、使用、出售、要约出售、导入和以其他方式运行、修改和 传播其贡献者版本的内容。
在以下三段中,“专利许可”是任何明示的 不执行专利的协议或承诺,无论名称如何 (例如明确允许实施专利或承诺不 起诉专利侵权)。将此类专利许可“授予”给 当事方是指作出此类协议或承诺不执行 对当事人不利的专利。
如果您在知情的情况下传达涵盖的作品,则依赖专利许可, 并且任何人都无法获得作品的相应来源 根据本许可的条款,通过以下方式免费复制 公开可用的网络服务器或其他易于访问的方式, 那么你必须 (1) 使相应的来源如此 可用,或 (2) 安排剥夺您自己从 本特定作品的专利许可,或 (3) 以某种方式安排 根据本许可的要求,延长专利 许可给下游接收者。“故意依赖”意味着你有 实际知道,但对于专利许可,您传达的 涵盖某个国家/地区的涵盖作品,或您的收件人对涵盖作品的使用 在一个国家,将侵犯一项或多项可识别的专利 您有理由相信有效的国家/地区。
如果根据单笔交易或与单笔交易有关,或 安排,您传达或通过采购运输来传播 涵盖的工作,并向某些当事方授予专利许可 接收授权他们使用、传播、修改的涵盖作品 或传达所涵盖作品的特定副本,然后获得专利许可 您的补助金将自动扩展到所涵盖的所有接受者 工作并在此基础上工作。
如果专利许可不包括在 其覆盖范围,禁止行使或以 不行使一项或多项特定权利 根据本许可授予。如果出现以下情况,您不得转让涵盖的作品 是与第三方达成的协议的一方,该第三方在 分发软件的业务,根据该业务,您向 第三方根据您传达 工作,以及第三方授予任何一方的工作 谁会从你那里获得涵盖的作品,这是一项歧视性专利 许可 (a) 与以下机构转交的涵盖作品的副本有关 您(或由这些副本制作的副本),或 (b) 主要用于和 in 与包含 涵盖的工作,除非您签订了该安排或该专利 在2007年3月28日之前颁发了许可证。
本许可中的任何内容均不得解释为排除或限制 任何可能涉及侵权的默示许可或其他抗辩理由 否则,根据适用的专利法,您可以使用。
如果条件强加给您(无论是通过法院命令、协议还是 否则)与本许可证的条件相矛盾,则不 请原谅您遵守本许可证的条件。如果您无法传达 涵盖的工作,以便同时履行您在 本许可和任何其他相关义务,则作为 因此,您可能根本不会传达它。例如,如果您同意 您有义务收取特许权使用费以进一步转让的条款 从您向其传达程序的人那里,这是您唯一可以的方式 同时满足这些条款,本许可将完全避免 从传达程序。
尽管本许可有任何其他规定,您仍拥有 允许将任何涵盖的作品与许可的作品链接或组合 在 GNU Affero 通用公共许可证的第 3 版下转换为单个 组合工作,并传达由此产生的工作。本条款 许可证将继续适用于所涵盖作品的部分, 但是GNU Affero通用公共许可证的特殊要求, 关于通过网络进行交互的第 13 节将适用于 这样的组合。
自由软件基金会可能会发布修订版和/或新版 不时的 GNU 通用公共许可证。这样的新 版本在精神上将与当前版本相似,但可能 在细节上有所不同,以解决新的问题或疑虑。
每个版本都有一个可区分的版本号。如果程序 指定 GNU General Public 的某个编号版本 许可证“或任何更高版本”适用于它,您可以选择 遵循该编号版本的条款和条件,或 自由软件基金会发布的任何更高版本。如果 程序没有指定 GNU General 的版本号 公共许可证,您可以选择任何由自由发布的版本 软件基础。
如果程序指定代理可以决定将来的版本 可以使用 GNU 通用公共许可证,该代理的公共 接受版本的声明永久授权您 为程序选择该版本。
更高版本的许可证可能会为您提供其他或不同的 权限。但是,没有对任何 作者或版权所有者,因为您选择遵循 更高版本。
在允许的范围内,该程序不作任何保证 适用法律。除非另有书面说明,否则版权 持有人和/或其他方“按原样”提供程序,而无需 任何形式的明示或暗示的保证,包括但不 仅限于对适销性和适用性的默示保证 一个特定的目的。关于质量和 程序的性能与您同在。如果程序证明 有缺陷的,您承担所有必要的服务、维修或费用 校正。
在任何情况下,除非适用法律要求或书面同意 任何版权所有者或任何其他修改和/或 在上述允许的情况下传达程序,对您承担损害赔偿责任, 包括任何一般、特殊、偶然或后果性损害 因使用或无法使用程序(包括 不限于数据丢失或数据不准确或 您或第三方遭受的损失或程序失败 与任何其他程序一起操作),即使此类持有人或其他 当事人已被告知此类损害的可能性。
如果免责声明和责任限制提供 上述内容不能根据其条款赋予当地法律效力, 复审法院应适用最接近的当地法律 绝对放弃与以下方面相关的所有民事责任 程序,除非附带保证或责任承担 程序的副本,以换取费用。
如果你开发了一个新程序,并且你希望它是最好的 可能用于公众,实现这一目标的最好方法是使它 每个人都可以在这些软件下重新分发和更改的自由软件 条款。
为此,请将以下通知附加到程序中。这是最安全的 将它们附加到每个源文件的开头,以最有效地 说明保修的排除;并且每个文件至少应具有 “版权”行和指向完整声明位置的指针。
one line to give the program's name and a brief idea of what it does. Copyright (C) year name of author This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/.
此外,还要添加有关如何通过电子邮件和纸质邮件与您联系的信息。
如果程序进行终端交互,则将其输出为短 当它以交互模式启动时,请注意如下:
program Copyright (C) year name of author This program comes with ABSOLUTELY NO WARRANTY; for details type ‘show w’. This is free software, and you are welcome to redistribute it under certain conditions; type ‘show c’ for details.
假设的命令 '' 和 '' 应显示 通用公共许可证的相应部分。当然,你的 程序的命令可能不同;对于 GUI 界面,您将 使用“关于框”。show wshow c
你还应该得到你的雇主(如果你是程序员)或学校, 如有,如有必要,为该程序签署“版权免责声明”。 有关这方面的更多信息,以及如何应用和遵循 GNU GPL,请参阅 https://www.gnu.org/licenses/。
GNU 通用公共许可证不允许将 程序到专有程序中。如果您的程序是子例程 库,您可能会认为允许链接专有更有用 带有库的应用程序。如果这是您要执行的操作,请使用 GNU 宽通用公共许可证,而不是本许可证。但 首先,请阅读 https://www.gnu.org/licenses/why-not-lgpl.html。
下一篇: 示例, 上一篇: GNU 通用公共许可证, 上一篇: Bison [内容][索引]
本章介绍了许多基本概念,没有这些概念,细节 野牛是没有意义的。如果您还不知道如何使用 Bison 或 Yacc,我们建议您从仔细阅读本章开始。
下一篇: 从形式规则到野牛输入,上一篇:野牛的概念【内容】[索引]
为了让 Bison 解析一种语言,它必须由上下文无关的语法来描述。这意味着您可以指定一个或多个语法分组,并给出从其构造它们的规则 部件。例如,在 C 语言中,一种分组称为 '表达式'。创建表达式的一条规则可能是,“表达式 可以由减号和另一个表达式组成“。另一个是, “表达式可以是整数”。正如你所看到的,规则往往是 递归的,但必须至少有一条规则导致 递归。
最常见的形式系统,用于呈现此类规则供人类阅读 是 Backus-Naur 形式或“BNF”,它是在 为了指定语言Algol 60。任何语法表示为 BNF 是一种与上下文无关的语法。Bison 的输入是 本质上是机器可读的 BNF。
上下文无关语法有各种重要的子类。虽然 它可以处理几乎所有与上下文无关的语法,Bison 针对什么进行了优化 称为 LR(1) 语法。简而言之,在这些语法中,它必须是可能的 了解如何仅使用单个标记解析输入字符串的任何部分 的展望。由于历史原因,Bison 默认受到 LALR(1) 的额外限制,这很难简单地解释。 有关这方面的更多信息,请参阅神秘的冲突。你可以逃跑 通过请求 IELR(1) 或规范 LR(1) 来获得这些附加限制 解析器表。请参阅 LR 工作台构造,了解如何操作。
LR(1) 语法的解析器是确定性的,这意味着 大致上,在输入中的任何一点应用的下一个语法规则是 由前面的输入和固定的有限部分唯一确定 (称为前瞻)剩余输入。与上下文无关 语法可能是模棱两可的,这意味着有多种方法可以 应用语法规则以获取相同的输入。甚至毫不含糊 语法可以是非确定性的,这意味着没有固定的 Lookahead 始终足以确定要应用的下一个语法规则。 通过适当的声明,Bison 也能够进一步解析这些内容 一般的上下文无关语法,使用一种称为 GLR 的技术 解析(对于广义 LR)。Bison 的 GLR 解析器 能够处理任何与上下文无关的语法,其 任何给定字符串的可能解析都是有限的。
在一种语言的正式语法规则中,每种句法单位 或分组由符号命名。那些通过分组构建的 根据语法规则,较小的结构称为非终端符号;那些不能细分的称为终端符号或令牌种类。我们调用一段输入 对应单个终端符号、令牌和件 对应于单个非终端符号的分组。
我们可以用 C 语言作为例子来说明什么符号、终端和 非终端,平均值。C 的标记是标识符、常量(数字 和字符串),以及各种关键字、算术运算符和 标点符号。因此,C 语法的终端符号包括 'identifier', 'number', 'string',加上每个关键字的一个符号, 运算符或标点符号: 'if', 'return', 'const', 'static', 'int', 'char', 'plus-sign', 'open-brace', 'close-brace', 'comma' 还有很多。 (这些标记可以细分为字符,但这是一个问题 词典编纂,而不是语法。
下面是一个简单的 C 函数,细分为令牌:
int /* keyword ‘int’ */ square (int x) /* identifier, open-paren, keyword ‘int’, identifier, close-paren */ { /* open-brace */ return x * x; /* keyword ‘return’, identifier, asterisk, identifier, semicolon */ } /* close-brace */
C 的句法分组包括表达式、语句、 声明和函数定义。这些都表示在 C语言的语法由非终端符号'expression'、'statement'、 “声明”和“函数定义”。完整的语法使用了几十个 其他语言结构,每个结构都有自己的非终端符号,在 为了表达这四个含义。上面的例子是一个 功能定义;它包含一个声明和一个语句。在 语句中,每个 '' 都是一个表达式,'' 也是如此。xx * x
每个非终端符号都必须有语法规则,显示它是如何制作的
从更简单的结构中。例如,一种 C 语句是语句;这将用一个语法规则来描述,该语法规则
非正式内容如下:return
“语句”可以由“return”关键字、“expression”和 '分号'。
“语句”还有许多其他规则,每种规则都有一个 C 语句。
必须将一个非终端符号区分为特殊符号,该符号 定义语言中的完整话语。它被称为开始 符号。在编译器中,这意味着一个完整的输入程序。在 C 语言,非终端符号“定义和声明的序列” 扮演这个角色。
例如,“”是有效的 C 表达式,即 C 的有效部分 程序,但它不能作为整个 C 程序使用。在 C 的上下文无关语法,这源于“表达式”是 不是开始符号。1 + 2
Bison 解析器读取一系列标记作为其输入,并将 使用语法规则的标记。如果输入有效,则最终结果为 整个标记序列简化为一个符号为 语法的开始符号。如果我们使用 C 的语法,则整个输入 必须是“定义和声明的序列”。如果没有,则解析器 报告语法错误。
形式语法是一种数学结构。定义语言 对于 Bison,您必须编写一个以 Bison 语法表达语法的文件: 一个 Bison 语法文件。请参阅 Bison 语法文件。
形式语法中的非终端符号在 Bison 输入中表示
作为标识符,就像 C 中的标识符一样。按照惯例,它应该是
小写,例如 ,或 。expr
stmt
declaration
终端符号的 Bison 表示也称为令牌
善良。令牌类型也可以表示为类似 C 的标识符。由
约定,这些标识符应为大写,以区分它们
非终端:例如、 、 或 。代表
语言应以转换为大写的关键字命名。
终端符号保留用于错误恢复。
请参阅符号、终端和非终端。INTEGER
IDENTIFIER
IF
RETURN
error
终端符号也可以表示为字符文字,就像 a C 字符常量。每当令牌只是一个 单个字符(括号、加号等):在 作为该令牌的终端符号的文本。
表示终端符号的第三种方法是使用 C 字符串常量 包含多个字符。有关详细信息,请参阅符号、终端和非终端。
语法规则在 Bison 语法中也有一个表达式。例如
这是 C 语句的 Bison 规则。中的分号
quotes 是一个文字字符标记,表示 C 语法的一部分
声明;裸分号和冒号是野牛标点符号
在每条规则中使用。return
stmt: RETURN expr ';' ;
请参阅语法规则。
下一篇: 语义动作, 上一篇: 从形式规则到野牛输入, 上一篇: 野牛的概念 [内容][索引]
形式语法仅通过标记的分类来选择标记:例如, 如果规则提到终端符号“整数常量”,则意味着任何整数常量在该位置在语法上都有效。这 常量的精确值与如何解析输入无关:如果 '' 是语法,那么 '' 或 '' 是平等的 语法。x+4x+1x+3989
但是,精确的值对于输入的含义非常重要 解析。如果编译器无法区分 4、1 和 3989 作为程序中的常量!因此,Bison 语法中的每个标记 具有标记类型和语义值。请参阅定义语言语义,了解 详。
标记类型是在语法中定义的终端符号,例如 或 。它告诉你的一切
需要知道以决定令牌可以有效显示的位置以及如何分组
它与其他代币。语法规则对标记一无所知,除了
他们的种类。INTEGER
IDENTIFIER
','
语义值包含有关
标记的含义,例如整数的值或
标识符。(诸如标点符号之类的标记不会
需要具有任何语义值。','
例如,输入标记可能被归类为标记类型,其语义值为 4。另一个输入令牌可能具有相同的内容
令牌种类,但值为 3989。当语法规则说这是允许的时,这些标记中的任何一个都是可以接受的,因为每个
是一个 .当解析器接受令牌时,它会跟踪
令牌的语义值。INTEGER
INTEGER
INTEGER
INTEGER
每个分组还可以具有语义值及其非终端值 象征。例如,在计算器中,表达式通常具有 语义值,即一个数字。在编程的编译器中 语言,表达式通常具有树的语义值 结构描述表达式的含义。
为了有用,程序必须做的不仅仅是解析输入;它必须 还可以根据输入产生一些输出。在野牛语法中,语法 rule 可以有一个由 C 语句组成的操作。每次 解析器识别出该规则的匹配项,并执行该操作。 请参阅操作。
大多数时候,操作的目的是计算语义值 从其各部分的语义值构建的整体结构。例如 假设我们有一个规则,说一个表达式可以是两个的总和 表达 式。当解析器识别出这样的总和时,每个 子表达式具有语义值,用于描述它是如何构建的。 此规则的操作应为 新识别的更大表达式。
例如,这里有一条规则说表达式可以是 两个子表达式:
expr: expr '+' expr { $$ = $1 + $3; } ;
该操作说明如何生成 sum 表达式的语义值 从两个子表达式的值。
在某些语法中,野牛的确定性 LR(1) 解析算法无法决定是否应用 给定点的某些语法规则。也就是说,它可能无法 决定(根据到目前为止读取的输入)两种可能中的哪一种 减少(语法规则的应用)适用,或是否适用 减少或阅读更多输入,并在稍后的 输入。这些分别称为减少/减少冲突 (请参阅减少/减少冲突)和转移/减少冲突 (请参阅 Shift/Reduce 冲突)。
使用一个不容易修改的语法是 LR(1),一个更通用的
有时需要解析算法。如果在文件中包含 Bison 声明(请参阅 Outline of a Bison Grammar),则
结果是广义 LR (GLR) 解析器。这些解析器处理 Bison
不包含未解决冲突的语法(即,在应用之后
优先级声明)与确定性解析器相同。然而
当面临未解决的移位/减少和减少/减少冲突时,GLR
解析器使用两者兼而有之的简单权宜之计,有效地克隆
解析器来遵循这两种可能性。每个生成的解析器都可以
再次拆分,因此在任何给定时间,都可以有任意数量的可能
正在探索的解析。解析器以同步方式进行;也就是说,所有
它们消耗(移位)给定的输入符号,然后再进行
下一个。每个克隆的解析器最终都会满足两种可能的解析器之一
命运:要么遇到解析错误,在这种情况下,它只是
消失,或者它与另一个解析器合并,因为它们两个有
将输入减少为一组相同的符号。%glr-parser
在有多个解析器期间,语义操作是 已录制,但未执行。当解析器消失时,它记录下来 语义操作也会消失,并且永远不会执行。当一个 减少使两个解析器相同,导致它们合并,Bison 记录 两组语义操作。每当最后两个解析器合并时, 回到单解析器的情况,Bison 解决了所有未解决的问题 通过对所涉及的语法规则的优先级或 执行这两个操作,然后调用指定的用户定义函数 以生成任意合并结果。
下一篇: 使用 GLR 解决歧义,上一篇: 编写 GLR 解析器 [Contents][Index]
在最简单的情况下,您可以使用 GLR 算法 解析明确但不能成为 LR(1) 的语法。 这种语法通常需要不止一个前瞻符号。
考虑一个问题 出现在枚举类型和子范围类型的声明中 编程语言 Pascal.以下是一些示例:
type subrange = lo .. hi; type enum = (a, b, c);
原始语言标准只允许使用数字文字和常量 子范围边界('' 和 '')的标识符,但 Extended Pascal (ISO/IEC 10206) 和许多其他 Pascal 实现允许任意 那里的表达式。这导致了以下情况,其中包含 多余的一对括号:lohi
type subrange = (a) .. b;
将此与以下枚举声明进行比较 键入只有一个值:
type enum = (a);
(这些声明是人为的,但它们在语法上是有效的,并且 更复杂的情况可能会出现在实际程序中。
这两个声明在“”标记之前看起来完全相同。跟 正常 LR(1) 单令牌前瞻,不可能在 解析标识符“”时的两种形式。然而,这是可取的 让解析器来决定这一点,因为在后一种情况下,“'”必须变成 新标识符表示枚举值,而在前一种情况下 '' 必须用其当前含义来评估,这可能是一个常数 甚至是函数调用。..aaa
您可以将“”解析为“括号中的未指定标识符”, 稍后解决,但这通常需要大量扭曲 语义动作和大部分语法,其中括号 嵌套在表达式的递归规则中。(a)
您可以考虑使用词法分析器来区分这两种形式 为当前定义和未定义的标识符返回不同的标记。 但是,如果这些声明发生在本地范围内,并且 一个外部范围,那么两种形式都是可能的——要么局部重新定义 '',或使用外部作用域中的值 ''。所以这个 方法行不通。aaa
此问题的简单解决方案是声明解析器以使用 GLR 算法。当 GLR 解析器达到临界状态时,它只是分裂 并同时遵循两个语法规则。早点或 后来,其中一个遇到解析错误。如果有“”令牌 在下一个 '' 之前,枚举类型的规则将失败,因为它 在任何地方都不能接受“”;否则,子范围类型规则将失败 因为它需要一个“”令牌。所以其中一个分支无声地失败了, 另一个继续正常,执行所有中间 在拆分期间推迟的操作。..;....
如果输入在语法上不正确,则两个分支都失败,并且解析器 像往常一样报告语法错误。
所有这一切的效果是解析器似乎“猜”到了正确的 分支来采取,或者换句话说,它似乎比 底层的 LR(1) 算法实际上允许。在此示例中,LR(2) 就足够了,但对于任何 k 来说,一些不是 LR(k) 的情况也可以这样处理。
通常,GLR 解析器可以采用二次或三次最坏情况时间,并且 对于某些人来说,当前的 Bison 解析器甚至需要指数级的时间和空间 语法。在实践中,这种情况很少发生,对于许多语法来说,确实如此 可能证明它不会发生。本示例仅包含 两个规则之间的一个冲突,以及包含 冲突不能嵌套。因此,可以存在于 任何时间都受到常数 2 的限制,解析时间仍然是线性的。
这是与上述示例相对应的 Bison 语法。它 解析 Pascal 类型声明的大幅简化形式。
%token TYPE DOTDOT ID
%left '+' '-' %left '*' '/'
%% type_decl: TYPE ID '=' type ';' ;
type: '(' id_list ')' | expr DOTDOT expr ;
id_list: ID | id_list ',' ID ;
expr: '(' expr ')' | expr '+' expr | expr '-' expr | expr '*' expr | expr '/' expr | ID ;
当用作正常的 LR(1) 语法时,Bison 会正确地抱怨 关于一个减少/减少冲突。在冲突的情况下, 解析器选择其中一个备选方案,任意选择一个 首先声明。因此,以下正确输入不是 认可:
type t = (a) .. b;
解析器可以变成 GLR 解析器,同时还可以告诉 Bison 要对一个已知的减少/减少冲突保持沉默,请添加 这两个声明对 Bison 语法文件(在第一个之前) ‘’):%%
%glr-parser %expect-rr 1
不需要改变语法本身。现在,解析器可以识别所有 根据上述有限的语法,有效的声明是透明的。 事实上,用户甚至没有注意到解析器何时分裂。
因此,这里我们有一个案例,我们可以利用 GLR 的优势,几乎没有 弊。然而,即使在像这样的简单情况下,至少也有 需要注意两个潜在的问题。首先,始终分析冲突 由 Bison 报告,以确保 GLR 拆分仅在原地完成 打算。无意中拆分 GLR 解析器可能会导致较少的问题 比 LR 解析器静态选择错误的替代方案更明显 冲突。其次,要非常谨慎地考虑与词法分析器的交互(参见 Token Kinds 中的语义信息)。由于拆分解析器使用没有 在拆分过程中执行任何操作,词法分析器都无法获得信息 通过解析器操作。词法分析器相互作用的一些情况可以通过以下方式消除 使用 GLR 将复杂功能从词法分析器转移到解析器。你必须 检查其余案例的正确性。
在我们的示例中,词法分析器可以安全地返回基于以下 它们在某个符号表中的当前含义,因为没有新符号 在类型声明的中间定义。虽然有可能 解析器,用于在解析枚举常量时定义枚举常量,在 类型声明完成,实际上没有区别,因为它们 不能在同一枚举类型声明中使用。
下一篇: GLR 语义操作,上一篇: 在明确语法上使用 GLR,上一篇: 编写 GLR 解析器 [内容][索引]
让我们考虑一个示例,从 C++ 大大简化 语法。1
%{ #include <stdio.h> int yylex (void); void yyerror (char const *); %} %define api.value.type {char const *} %token TYPENAME ID %right '=' %left '+' %glr-parser %% prog: %empty | prog stmt { printf ("\n"); } ; stmt: expr ';' %dprec 1 | decl %dprec 2 ; expr: ID { printf ("%s ", $$); } | TYPENAME '(' expr ')' { printf ("%s <cast> ", $1); } | expr '+' expr { printf ("+ "); } | expr '=' expr { printf ("= "); } ; decl: TYPENAME declarator ';' { printf ("%s <declare> ", $1); } | TYPENAME declarator '=' expr ';' { printf ("%s <init-declare> ", $1); } ; declarator: ID { printf ("\"%s\" ", $1); } | '(' declarator ')' ;
这模拟了 C++ 语法的一个有问题的部分——之间的歧义 某些声明和声明。例如
T (x) = y+z;
解析为 an 或 a(假设 '' 被识别为 a 和
'' 作为 )。
Bison 将此检测为规则 和 之间的 reduce/reduce 冲突,它无法在
在上面的示例中遇到的时间。由于这是一个
GLR 解析器,因此它将问题拆分为两个解析,一个用于
解决 reduce/reduce 冲突的每个选择。
与上一节中的示例不同(请参阅在明确语法上使用 GLR),
然而,这些解析都没有“死亡”,因为它的语法是
模糊。其中一个解析器最终会减少和
另一个 reduce ,之后两个解析器都处于
相同的状态:他们看到了“”,并且具有相同的未处理状态
剩余输入。我们说这些解析已经合并。expr
stmt
TTYPENAME
xID
expr : ID
declarator : ID
x
stmt : expr ';'
stmt : decl
prog stmt
此时,GLR 解析器需要
如何在竞争解析之间进行选择的语法。
在上面的示例中,两个声明指定 Bison 优先
到将示例解释为 的解析,这意味着 that 是一个声明器。
因此,解析器打印%dprec
decl
x
"x" y z + T <init-declare>
只有当多个声明时,这些声明才会发挥作用
解析幸存下来。请考虑此解析器的不同输入字符串:%dprec
T (x) + y;
这是使用 GLR 解析明确
构造,如上一节所示(请参阅在明确语法上使用 GLR)。
在这里,没有歧义(这不能解析为声明)。
但是,当 Bison 解析器遇到 时,它没有
有足够的信息来解决减少/减少冲突(再次,
之间作为 an 或 a )。在这个
case,则不使用优先级声明。同样,解析器分裂
一分为二,一个假设是 ,另一个假设是
假设是 .这些解析器中的第二个
然后在看到 时消失,解析器打印x
x
expr
declarator
x
expr
x
declarator
+
x T <cast> y +
假设您不想解决歧义,而是想看到所有内容
可能性。为此,必须合并语义
两个可能的解析器的操作,而不是选择一个而不是
其他。为此,您可以更改
遵循:stmt
stmt: expr ';' %merge <stmt_merge> | decl %merge <stmt_merge> ;
并将函数定义为:stmt_merge
static YYSTYPE stmt_merge (YYSTYPE x0, YYSTYPE x1) { printf ("<OR> "); return ""; }
并附有远期声明 在文件开头的 C 声明中:
%{ static YYSTYPE stmt_merge (YYSTYPE x0, YYSTYPE x1); %}
使用这些声明,生成的分析器将分析第一个示例
作为 an 和 a ,并打印expr
decl
"x" y z + T <init-declare> x T <cast> y z + = <OR>
Bison 要求所有 参与任何特定合并的作品具有相同的 '' 子句。否则,歧义将无法解决, 解析器将在任何解析期间报告错误,导致 有问题的合并。%merge
合并的签名取决于符号的类型。在
前面的示例中,合并到符号 () 没有
特定类型,合并为stmt
YYSTYPE stmt_merge (YYSTYPE x0, YYSTYPE x1);
但是,如果具有声明的类型,例如,stmt
%type <Node *> stmt;
或
%union { Node *node; ... };
%type <node> stmt;
那么合并的原型必须是:
Node *stmt_merge (YYSTYPE x0, YYSTYPE x1);
(这个签名最初可能是一个错误,也许它应该是一个错误 ‘’.如果您对以下内容有意见 它,请告诉我们。Node *stmt_merge (Node *x0, Node *x1)
下一篇: 使用任意谓词控制解析, 上一篇: 使用 GLR 解决歧义, 上一篇: 编写 GLR 解析器 [Contents][索引]
GLR 解析的性质和生成的结构 解析器对语义值和操作产生了某些限制。
根据定义,延迟语义操作不会同时执行 相关的减少。 这引发了您可能在语义中使用的几个 Bison 特征的警告 GLR 解析器中的操作。
在任何语义操作中,您都可以检查以确定类型
在相关减少时存在的 Lookahead 令牌。
检查未设置为 或 后,可以检查 和
确定 Lookahead 令牌的语义值和位置(如果有)。在
非延迟语义操作,您还可以将这些变量中的任何一个修改为
影响句法分析。请参阅 Lookahead 令牌。yychar
yychar
YYEMPTY
YYEOF
yylval
yylloc
在延迟语义操作中,影响语法分析为时已晚。
在本例中,、 和 设置为
它们在相关减少时所拥有的值的浅拷贝。
仅出于这个原因,修改它们就很危险。
此外,修改它们的结果是未定义的,并且可能会随着
野牛的未来版本。
例如,如果语义操作可能被推迟,则永远不应该编写它
调用(请参阅在操作中使用的特殊功能)或尝试释放
引用的内存。yychar
yylval
yylloc
yyclearin
yylval
另一个需要特别考虑的 Bison 功能是(请参阅在操作中使用的特殊功能),您可以在语义操作中调用它来
启动错误恢复。
在确定性 GLR 操作期间,效果为
与它在确定性解析器中的效果相同。
推迟行动中的效果是相似的,但
error 未定义;相反,解析器将恢复为确定性操作,
选择要继续处理语法错误的未指定堆栈。
在语义谓词中(请参阅使用任意谓词控制解析)期间
解析,默默修剪
调用测试的分析。YYERROR
YYERROR
YYERROR
GLR 解析器要求您使用 POD(纯旧数据)类型 使用生成的解析器时的语义值和位置类型 C++ 代码。
上一篇: GLR 语义操作, 上一篇: 编写 GLR 解析器 [内容][索引]
除了 和 指令之外,
GLR 解析器
允许您根据执行的任意计算拒绝解析
在用户代码中,而没有让 Bison 将此拒绝视为错误
如果有替代解析。例如%dprec
%merge
widget: %?{ new_syntax } "widget" id new_args { $$ = f($3, $4); } | %?{ !new_syntax } "widget" id old_args { $$ = f($3, $4); } ;
是允许同一解析器处理两种不同语法的一种方法
部件。前面的从句被视为普通条款
Midrule 操作,除了其文本作为表达式处理并且始终
立即评估(即使在非确定性模式下)。如果
表达式产生 0 (false),该子句被视为语法错误,
在非确定性解析器中,这会导致减少它的堆栈
去死。在确定性解析器中,它的行为类似于 .%?
YYERROR
如示例所示,谓词在其他方面看起来像语义操作,并且 因此,在确定数字时必须考虑它们 用于表示右侧符号的语义值。 但是,谓词动作没有定义的值,并且可能不会给出 标签。
语义谓词和普通谓词之间存在细微的区别 非确定性模式下的操作,因为后者是延迟的。 例如,我们可以尝试将前面的示例重写为
widget: { if (!new_syntax) YYERROR; } "widget" id new_args { $$ = f($3, $4); } | { if (new_syntax) YYERROR; } "widget" id old_args { $$ = f($3, $4); } ;
(颠倒谓词测试的意义,当它们
false)。但是,这
如果 和 具有重叠的语法,则不具有相同的效果。
由于 midrule 操作测试被推迟,
GLR 解析器首先遇到未解决的模棱两可的约简
对于在执行 的测试之前识别同一字符串的情况。因此,它
报告错误。new_args
old_args
new_syntax
new_args
old_args
new_syntax
最后,在写谓词时要小心:推迟的操作没有 assessd,因此在谓词中使用它们将产生未定义的效果。
下一篇: Bison 输出:解析器实现文件, 上一篇: 编写 GLR 解析器, 上一篇: Bison 的概念 [内容][索引]
许多应用程序(如解释器或编译器)必须产生冗长的内容 和有用的错误消息。为此,必须能够跟踪 每个句法结构的文本位置或位置。 Bison 提供了一种处理这些位置的机制。
每个标记都有一个语义值。以类似的方式,每个令牌都有一个 关联的位置,但所有令牌的位置类型相同 和分组。此外,输出解析器配备了默认数据 用于存储位置的结构(有关详细信息,请参阅跟踪位置 详细信息)。
与语义值一样,可以使用专用的
构造集。在上面的示例中,整个分组的位置
是 ,而子表达式的位置是 和 。@$
@1
@3
匹配规则时,将使用默认操作来计算语义值
(请参阅操作)。以同样的方式,另一个默认值
操作用于位置。但是,针对位置的操作是一般性的
对于大多数情况来说已经足够了,这意味着通常不需要对每个情况进行描述
规则应该如何形成。为给定位置构建新位置时
分组时,输出解析器的默认行为是取开头
第一个符号和最后一个符号的末尾。@$
当你运行 Bison 时,你给它一个 Bison 语法文件作为输入。这 最重要的输出是一个 C 源文件,它实现了 语法描述的语言。此解析器称为 Bison 解析器,此文件称为 Bison 解析器 实现文件。请记住,Bison 实用程序和 Bison 解析器是两个不同的程序:Bison 实用程序是一个程序 其输出是成为一部分的 Bison 解析器实现文件 你的程序。
Bison 解析器的工作是根据 语法规则 - 例如,将标识符和运算符构建到 表达 式。当它这样做时,它会运行语法规则的操作 使用。
标记来自一个名为词法分析器的函数,该函数
您必须以某种方式提供(例如用 C 编写它)。野牛
分析器每次需要新令牌时都会调用词法分析器。它
不知道令牌的“内部”是什么(尽管它们的语义值
可能反映了这一点)。通常,词法分析器通过以下方式制作标记
解析文本字符,但 Bison 不依赖于此。
请参阅 Lexical Analyzer 函数 yylex
。
Bison 解析器实现文件是 C 代码,它定义了
函数命名,用于实现该语法。这
函数不能制作一个完整的 C 程序:您必须提供一些
附加功能。一个是词汇分析器。另一个是
错误报告函数,解析器调用该函数来报告错误。
此外,一个完整的 C 程序必须从一个名为 ;您必须提供此信息,并安排它调用,否则解析器将永远无法运行。请参阅分析器 C 语言接口。yyparse
main
yyparse
除了令牌种类名称和操作中的符号外,您
write,Bison解析器实现文件中定义的所有符号
本身以 '' 或 '' 开头。这包括接口
函数,例如词法分析器函数、
错误报告函数和解析器函数本身。这还包括使用的众多标识符
用于内部目的。因此,您应该避免使用 C
Bison 语法中以“”或“”开头的标识符
文件,但本手册中定义的文件除外。另外,你应该
避免使用 C 标识符 '' 和 ''
除了通常的含义之外的任何内容。yyYYyylex
yyerror
yyparse
yyYYmallocfree
在某些情况下,Bison 解析器实现文件包括 system
标头,在这些情况下,您的代码应遵循标识符
由这些标头保留。在某些非 GNU 主机上,包含 、 、 (如果可用) 和 来声明内存分配器和整数类型和常量。 如果正在使用消息翻译,则包括在内
(请参阅解析器国际化)。可能包括其他系统标头
如果将(请参阅跟踪解析器)或(请参阅野牛符号)定义为非零值。<limits.h>
<stddef.h>
<stdint.h>
<stdlib.h>
<libintl.h>
YYDEBUG
YYSTACK_USE_ALLOCA
下一篇: Bison 语法的整体布局,上一篇: Bison 输出:解析器实现文件,上一篇:Bison 的概念 [内容][索引]
使用 Bison 的实际语言设计过程,来自语法规范 对于工作编译器或解释器,具有以下部分:
yylex
)。它
也可以使用 Lex 生成,但 Lex 的使用未在
本手册。要将编写的源代码转换为可运行的程序,您需要 必须按照以下步骤操作:
Bison 实用程序的输入文件是 Bison 语法文件。这 Bison 语法文件的一般形式如下:
%{ Prologue %} Bison declarations %% Grammar rules %% Epilogue
“'”、“'”和“”是出现的标点符号 在每个 Bison 语法文件中分隔各部分。%%%{%}
序言可以定义操作中使用的类型和变量。您可以
此外,使用预处理器命令来定义此处使用的宏,并用于包含执行任何这些操作的头文件。
您需要声明词法分析器和错误
打印机,以及任何其他全局标识符
由语法规则中的操作使用。#include
yylex
yyerror
Bison 声明声明终端和非终端的名称 符号,还可以描述运算符优先级和 各种符号的语义值。
语法规则定义了如何从其构造每个非终端符号 部件。
结语可以包含要使用的任何代码。通常 序言中声明的函数定义请点击此处。在 简单的程序,程序的其余部分都可以转到这里。
现在我们展示并解释几个使用 Bison 编写的示例程序: 反向波兰符号计算器,一种代数(中缀)符号 计算器 — 后来扩展到跟踪“位置” — 以及多功能计算器。都 生产可用但有限的交互式桌面计算器。
这些例子很简单,但实际编程的 Bison 语法 语言的编写方式相同。您可以将这些示例复制到 源文件来尝试它们。
Bison 附带了几个示例(包括针对不同目标的示例) 语言)。如果此软件包安装正确,您将在 中找到它们,根目录在哪里 的安装,可能类似于 或 .prefix/share/doc/bison/examplesprefix/usr/local/usr
下一篇: 中缀符号计算器: calc
, Up: 示例 [内容][索引]
第一个示例2 是简单的双精度反向 波兰语 符号计算器(使用后缀运算符的计算器)。此示例 提供了一个很好的起点,因为运算符优先级不是问题。 第二个示例将说明如何处理运算符优先级。
此计算器的源代码名为 。这 '' 扩展名是用于 Bison 语法文件的约定。rpcalc.y.y
下一篇: rpcalc
语法规则 上一篇: 反向波兰语符号计算器 [内容][索引]
rpcalc
以下是反向波兰符号的 C 和 Bison 声明 计算器。与 C 语言一样,注释位于 '' 或 在“”之后。/*…*///
/* Reverse Polish Notation calculator. */
%{ #include <stdio.h> #include <math.h> int yylex (void); void yyerror (char const *); %}
%define api.value.type {double} %token NUM %% /* Grammar rules and actions follow. */
声明部分(见序言)包含两个 预处理器指令和两个前向声明。
该指令用于声明幂
功能。#include
pow
和 的转发声明是
需要,因为 C 语言要求声明函数
在使用之前。这些函数将在
结语,但解析器调用它们,因此必须在
序幕。yylex
yyerror
第二部分,野牛声明,向野牛提供关于 令牌及其类型(参见 Bison 声明部分)。
该指令定义变量 ,
从而为标记和
分组(请参阅语义值的数据类型)。野牛
解析器将使用定义为的任何类型;如果你
不要定义它,是默认设置。因为我们指定
'',每个标记和每个表达式都有一个关联的值,
这是一个浮点数。C代码可以用来指代
值 。%define
api.value.type
api.value.type
int
{double}YYSTYPE
api.value.type
每个不是单字符文字的终端符号都必须是
宣布。(单字符文字通常不需要声明。
在此示例中,所有算术运算符都由
单字符文字,因此唯一需要的终端符号
声明为 ,数值常量的标记类型。NUM
下一篇: rpcalc
词汇分析器, 上一篇: rpcalc
声明, 上一篇: 反向波兰符号计算器 [内容][索引]
rpcalc
以下是反向波兰语符号计算器的语法规则。
input: %empty | input line ;
line: '\n' | exp '\n' { printf ("%.10g\n", $1); } ;
exp: NUM | exp exp '+' { $$ = $1 + $2; } | exp exp '-' { $$ = $1 - $2; } | exp exp '*' { $$ = $1 * $2; } | exp exp '/' { $$ = $1 / $2; } | exp exp '^' { $$ = pow ($1, $2); } /* Exponentiation */ | exp 'n' { $$ = -$1; } /* Unary minus */ ;
%%
此处定义的 rpcalc“语言”的分组是表达式
(给定名称)、输入行 () 和
完整的输入成绩单 ()。这些非终端中的每一个
符号有几个替代规则,由竖条“”连接
读作“或”。以下各节介绍了这些规则的内容
意味 着。exp
line
input
|
语言的语义是由当 识别分组。这些操作是出现在里面的 C 代码 括号。请参阅操作。
您必须在 C 中指定这些操作,但 Bison 提供了
在规则之间传递语义值。在每个操作中,
伪变量代表分组的语义值
规则将要构建。赋值 是
大多数动作的主要工作。组件的语义值
规则称为 、 等。$$
$$
$1
$2
下一篇: 行
说明, 上一篇: rpcalc
的语法规则 [内容][索引]
input
考虑以下定义:input
input: %empty | input line ;
该定义如下:“完整的输入要么是空的
字符串,或一个完整的输入后跟一行输入“。请注意
“完整输入”是根据其本身来定义的。这个定义是说的
要保持递归,因为总是显示为
序列中最左边的符号。请参阅递归规则。input
第一个备选方案为空,因为
冒号和第一个“”;这意味着可以匹配
输入的空字符串(无标记)。我们以这种方式编写规则,因为它
在启动计算器后立即键入是合法的。
按照惯例,首先放置一个空的替代方案,然后使用
(可选)指令,或在其中写上注释 ''(参见空规则)。|input
Ctrl-d%empty
/* empty
*/
第二个备用规则 () 处理所有非平凡的输入。
它的意思是,“在阅读任意数量的行后,如果
可能。左递归使此规则形成一个循环。由于
第一个备选方案匹配空输入,循环可以执行零或
更多次。input line
解析器函数继续处理输入,直到
出现语法错误或词汇分析器说没有更多
输入令牌;我们将安排后者在输入结束时发生。yyparse
下一篇: exp
说明, 上一篇: 输入
说明, 上一篇: rpcalc
语法规则 [内容][索引]
line
现在考虑以下定义:line
line: '\n' | exp '\n' { printf ("%.10g\n", $1); } ;
第一种替代方法是换行符的标记;这意味着
该 RPCALC 接受一个空行(并忽略它,因为没有
行动)。第二种选择是表达式后跟换行符。
这是使 rpcalc 有用的替代方案。的语义值
分组是 的值,因为
问题 是备选方案中的第一个符号。该操作将打印此内容
value,这是用户请求的计算结果。exp
$1
exp
此操作是不寻常的,因为它不会将值赋给 。如
结果,与 是 关联的语义值
未初始化(其值将不可预测)。如果出现以下情况,这将是一个错误
该值曾经被使用过,但我们不使用它:一旦 RPCALC 打印了
值,则不再需要该值。$$
line
上一篇: 行
说明, 上一篇: rpcalc
语法规则 [内容][索引]
exp
分组有多个规则,每种表达式一个规则。
第一条规则处理最简单的表达式:那些只是
数字。第二个处理一个加法表达式,它看起来像两个
表达式后跟加号。第三个处理减法,等等
上。exp
exp: NUM | exp exp '+' { $$ = $1 + $2; } | exp exp '-' { $$ = $1 - $2; } … ;
我们使用 '' 来连接 的所有规则,但我们可以
同样,将它们分开编写:|exp
exp: NUM; exp: exp exp '+' { $$ = $1 + $2; }; exp: exp exp '-' { $$ = $1 - $2; }; …
大多数规则都具有计算表达式值的操作
其部分价值的条款。例如,在加法规则中,引用第一个组件并引用
第二个。第三个组件 ,没有意义
关联的语义值,但如果它有语义值,您可以将其称为 .第一条规则依赖于隐式默认操作:“'”。$1
exp
$2
'+'
$3
{
$$ = $1; }
当使用此规则识别求和表达式时,总和
两个子表达式的值作为整体的值生成
表达。请参阅操作。yyparse
您不必为每条规则都执行操作。当规则没有操作时,
默认情况下,Bison 将 的值复制到 。这就是
发生在第一条规则(使用 的那条规则)中。$1
$$
NUM
此处显示的格式是推荐的约定,但 Bison 不是 需要它。您可以根据需要添加或更改空格。为 例如:
exp: NUM | exp exp '+' {$$ = $1 + $2; } | … ;
含义与此相同:
exp: NUM | exp exp '+' { $$ = $1 + $2; } | … ;
然而,后者更具可读性。
下一篇: 控制功能, 上一篇: rpcalc
语法规则, 上一篇: 反向波兰符号计算器 [内容][索引]
rpcalc
词汇分析器的工作是低级解析:转换字符
或将字符序列转换为标记。Bison 解析器得到它的
通过调用词法分析器来标记。请参阅 Lexical Analyzer 函数 yylex
。
RPN 只需要一个简单的词法分析器
计算器。这
词法分析器跳过空格和制表符,然后读取数字 as 并将它们作为标记返回。任何其他字符
那不是数字的一部分,而是一个单独的令牌。请注意,令牌代码
对于这样的单字符标记就是字符本身。double
NUM
词法分析器函数的返回值是一个数字代码,它
表示令牌类型。野牛规则中使用的相同文本代表
此标记类型也是该类型的数字代码的 C 表达式。
这以两种方式工作。如果令牌类型是字符文字,则其
数字代码是字符的数字代码;您可以使用相同的字符
字面上表示数字。如果令牌种类是
一个标识符,该标识符由 Bison 定义为 C 枚举,其
definition 是适当的代码。因此,在此示例中,成为要使用的枚举。NUM
yylex
令牌的语义值(如果有)存储在全局
变量 ,这是 Bison 解析器将查找它的位置。
(的 C 数据类型是 ,其值已定义
在语法的开头通过“”;请参阅 rpcalc
的声明。yylval
yylval
YYSTYPE
%define api.value.type
{double}
如果遇到输入结束,则返回令牌种类代码 0。 (Bison 将任何非正值识别为表示输入结束。
下面是词法分析器的代码:
/* The lexical analyzer returns a double floating point number on the stack and the token NUM, or the numeric code of the character read if not a number. It skips all blanks and tabs, and returns 0 for end-of-input. */ #include <ctype.h> #include <stdlib.h>
int yylex (void) { int c = getchar (); /* Skip white space. */ while (c == ' ' || c == '\t') c = getchar ();
/* Process numbers. */ if (c == '.' || isdigit (c)) { ungetc (c, stdin); if (scanf ("%lf", &yylval) != 1) abort (); return NUM; }
/* Return end-of-input. */ else if (c == EOF) return YYEOF; /* Return a single char. */ else return c; }
Next: 错误报告例程, Previous: rpcalc
Lexical Analyzer, Up: Reverse Polish Notation Calculator [Contents][Index]
根据这个例子的精神,控制功能是
保持在最低限度。唯一的要求是它调用以启动解析过程。yyparse
int main (void) { return yyparse (); }
下一篇: 运行 Bison 制作解析器, 上一篇: 控制函数, 上一篇: 反向波兰符号计算器 [内容][索引]
当检测到语法错误时,它会调用错误报告
函数打印错误消息(通常但不是
总是 )。由程序员提供(参见解析器 C 语言接口),所以
以下是我们将使用的定义:yyparse
yyerror
"syntax error"
yyerror
#include <stdio.h>
/* Called by yyparse on error. */ void yyerror (char const *s) { fprintf (stderr, "%s\n", s); }
返回后,Bison 解析器可能会从错误中恢复
如果语法包含合适的错误规则,则继续解析
(请参阅错误恢复)。否则,返回非零。我们
在此示例中没有编写任何错误规则,因此任何无效的输入都会
导致计算器程序退出。这不是干净的行为
真正的计算器,但对于第一个例子来说已经足够了。yyerror
yyparse
在运行 Bison 生成解析器之前,我们需要决定如何
将所有源代码排列在一个或多个源文件中。对于这样的
简单的例子,最简单的事情就是把所有东西都放在一个文件中,
语法文件。和 的定义在语法文件的尾声中
(参见 The Overall Layout of a Bison Grammar)。yylex
yyerror
main
对于一个大型项目,您可能有多个源文件,并用于安排重新编译它们。make
对于语法文件中的所有源代码,您可以使用以下命令 要将其转换为解析器实现文件,请执行以下操作:
$ bison file.y
在此示例中,语法文件被调用 (for
“反向波兰 CALCulator”)。Bison 生成解析器
实现文件,删除了
'' 从语法文件名中。分析器实现文件
包含 的源代码。附加功能
在语法文件中 (, 和 ) 是
逐字复制到解析器实现文件。rpcalc.yfile.tab.c.yyyparse
yylex
yyerror
main
上一篇: 运行 Bison 制作解析器,向上: 反向波兰符号计算器 [内容][索引]
以下是编译和运行解析器实现文件的方法:
# List files in current directory.
$ ls
rpcalc.tab.c rpcalc.y
# Compile the Bison parser.
# -lm tells compiler to search math library for pow
.
$ cc -lm -o rpcalc rpcalc.tab.c
# List files again.
$ ls
rpcalc rpcalc.tab.c rpcalc.y
该文件现在包含可执行代码。这是一个
使用 的示例会话。rpcalcrpcalc
$ rpcalc 4 9 + ⇒ 13 3 7 + 3 4 5 *+- ⇒ -13 3 7 + 3 4 5 * + - n Note the unary minus, ‘n’ ⇒ 13 5 6 / 4 n + ⇒ -3.166666667 3 4 ^ Exponentiation ⇒ 81 ^D End-of-file indicator $
calc
我们现在修改 rpcalc 来处理中缀运算符,而不是 后缀。3 中缀 表示法涉及运算符优先级的概念和 嵌套到任意深度的括号。这是 的 Bison 代码,一个中缀桌面计算器。calc.y
/* Infix notation calculator. */
%{ #include <math.h> #include <stdio.h> int yylex (void); void yyerror (char const *); %}
/* Bison declarations. */ %define api.value.type {double} %token NUM %left '-' '+' %left '*' '/' %precedence NEG /* negation--unary minus */ %right '^' /* exponentiation */
%% /* The grammar follows. */
input: %empty | input line ;
line: '\n' | exp '\n' { printf ("\t%.10g\n", $1); } ;
exp: NUM | exp '+' exp { $$ = $1 + $3; } | exp '-' exp { $$ = $1 - $3; } | exp '*' exp { $$ = $1 * $3; } | exp '/' exp { $$ = $1 / $3; } | '-' exp %prec NEG { $$ = -$2; } | exp '^' exp { $$ = pow ($1, $3); } | '(' exp ')' { $$ = $2; } ;
%%
函数 ,可以是
和以前一样。yylex
yyerror
main
此代码中显示了两个重要的新功能。
在第二部分(Bison 声明)中,声明令牌
种类,并说它们是左关联运算符。声明 和 (右关联性) 取代了用于声明令牌种类名称而不
关联性/优先级。(这些标记是单字符文字,
通常不需要声明。我们在这里声明它们是为了指定
关联性/优先级。%left
%left
%right
%token
运算符优先级由
声明;声明的行号越大(越低
页面或屏幕),优先级越高。因此,幂
优先级最高,一元减号()紧随其后
通过 '' 和 '',依此类推。一元减号不具有关联性,
只有优先级很重要(.请参阅运算符优先级。NEG
*/%precedence
另一个重要的新功能是语法
部分。简单的指示
Bison 认为规则 '' 具有相同的优先级,在本例中为 次高。请参阅上下文相关优先级。%prec
%prec
| '-' expNEG
下面是一个示例运行:calc.y
$ calc 4 + 4.5 - (34/(8*3+-3)) 6.880952381 -56 + 2 -54 3 ^ 2 9
下一篇: 位置跟踪计算器: ltcalc
, 上一篇: 中缀符号计算器: calc
, 上一篇: 示例 [内容][索引]
到目前为止,本手册尚未解决错误问题
恢复 - 如何在解析器检测到语法后继续解析
错误。我们处理的只是错误报告。
回想一下,默认情况下,调用 后返回 。这意味着错误的输入行会导致
计算器程序退出。现在我们展示如何纠正这一缺陷。yyerror
yyparse
yyerror
野牛语言本身包括保留词,它
可以包含在语法规则中。在下面的示例中,它有
已添加到以下替代项之一:error
line
line: '\n' | exp '\n' { printf ("\t%.10g\n", $1); } | error '\n' { yyerrok; } ;
语法的这一补充允许在
语法错误事件。如果无法计算的表达式是
读取,则该错误将被第三条规则识别为 ,
解析将继续。(该函数仍被调用
也打印其消息。该操作执行语句,一个由Bison自动定义的宏;它的意思是
错误恢复已完成(请参阅错误恢复)。请注意
和 之间的区别 ;两者都不是
错字。line
yyerror
yyerrok
yyerrok
yyerror
这种形式的错误恢复处理语法错误。还有其他
各种错误;例如,除以零,这会引发异常
通常致命的信号。真正的计算器程序必须处理这个问题
信号和使用返回和恢复解析
输入线;它还必须丢弃当前行的其余部分
输入。我们不会进一步讨论这个问题,因为它不是特定于
野牛程序。longjmp
main
下一篇: 多功能计算器: mfcalc
, 上一篇: 简单错误恢复, 上一篇: 示例 [内容][索引]
ltcalc
本示例使用位置扩展中缀表示法计算器 跟踪。此功能将用于改进错误消息。为 为了清楚起见,这个例子是一个简单的整数计算器,因为 使用位置所需的大部分工作将在词典中完成 分析器。
下一篇: ltcalc
语法规则, 上一篇: 位置跟踪计算器: ltcalc
[内容][索引]
ltcalc
位置跟踪计算器的 C 和 Bison 声明是 与中缀表示法计算器的声明相同。
/* Location tracking calculator. */ %{ #include <math.h> int yylex (void); void yyerror (char const *); %} /* Bison declarations. */ %define api.value.type {int} %token NUM %left '-' '+' %left '*' '/' %precedence NEG %right '^' %% /* The grammar follows. */
请注意,没有特定于位置的声明。定义数据类型
对于不需要存储位置:我们将使用
默认值(请参阅位置的数据类型),这是一个四成员结构,具有
以下整数字段:、 和 。按照惯例,并按照
根据 GNU 编码标准和惯例,行数和列数
两者都从 1 开始。first_line
first_column
last_line
last_column
下一篇: ltcalc
词汇分析器, 上一篇: ltcalc
声明, 上一篇: 位置跟踪计算器: ltcalc
[内容][索引]
ltcalc
是否处理位置对 your 的语法没有影响 语言。因此,此示例的语法规则将非常接近 对于上一个示例中的那些:我们只会修改它们以使其受益 从新信息。
在这里,我们将使用位置来报告除以零,并找到 错误的表达式或子表达式。
input: %empty | input line ;
line: '\n' | exp '\n' { printf ("%d\n", $1); } ;
exp: NUM | exp '+' exp { $$ = $1 + $3; } | exp '-' exp { $$ = $1 - $3; } | exp '*' exp { $$ = $1 * $3; }
| exp '/' exp { if ($3) $$ = $1 / $3; else { $$ = 1; fprintf (stderr, "%d.%d-%d.%d: division by zero", @3.first_line, @3.first_column, @3.last_line, @3.last_column); } }
| '-' exp %prec NEG { $$ = -$2; } | exp '^' exp { $$ = pow ($1, $3); } | '(' exp ')' { $$ = $2; }
此代码演示如何通过以下方式到达语义操作中的位置
使用规则组件的伪变量,以及
分组的伪变量。@n
@$
我们不需要为:输出解析器赋值
自然而然。默认情况下,在执行每个操作的 C 代码之前,对于包含组件的规则,设置为从 的开头到结尾的范围。此行为可以是
重新定义(请参阅位置的默认操作),对于非常具体的规则,可以手动计算。@$
@$
@1
@n
n@$
上一篇: ltcalc
语法规则, 上一篇: 位置跟踪计算器: ltcalc
[内容][索引]
ltcalc
到目前为止,我们依靠 Bison 的默认设置来启用位置 跟踪。下一步是重写词法分析器,并使其 能够向解析器提供令牌位置,就像它已经为 语义值。
为此,我们必须考虑到 输入文本,以避免计算位置模糊或错误:
int yylex (void) { int c;
/* Skip white space. */ while ((c = getchar ()) == ' ' || c == '\t') ++yylloc.last_column;
/* Step. */ yylloc.first_line = yylloc.last_line; yylloc.first_column = yylloc.last_column;
/* Process numbers. */ if (isdigit (c)) { yylval = c - '0'; ++yylloc.last_column; while (isdigit (c = getchar ())) { ++yylloc.last_column; yylval = yylval * 10 + c - '0'; } ungetc (c, stdin); return NUM; }
/* Return end-of-input. */ if (c == EOF) return YYEOF;
/* Return a single char, and update location. */ if (c == '\n') { ++yylloc.last_line; yylloc.last_column = 0; } else ++yylloc.last_column; return c; }
基本上,词法分析器执行与以前相同的处理:它
跳过空格和制表符,并读取数字或单字符标记。在
此外,它还会更新包含令牌位置的全局变量(类型)。yylloc
YYLTYPE
现在,每次此函数返回令牌时,解析器的类型为
以及它的语义价值,以及它在文本中的位置。最后需要的
更改是初始化,例如在控制
功能:yylloc
int main (void) { yylloc.first_line = yylloc.last_line = 1; yylloc.first_column = yylloc.last_column = 0; return yyparse (); }
请记住,计算位置不是语法问题。每 字符必须与位置更新相关联,无论它是否在 有效输入、注释、文本字符串等。
下一篇: 练习, 上一篇: 位置跟踪计算器: ltcalc
, 上一篇: 示例 [内容][索引]
mfcalc
既然已经讨论了 Bison 的基础知识,现在是时候继续讨论
更高级的问题。4 以上计算器仅提供
五个功能,''、''、''、''和''。它
如果有一个提供其他数学的计算器就好了
函数,如 、 等。+-*/^sin
cos
很容易将新的运算符添加到中缀计算器中,只要它们是
只有单字符文字。词法分析器通过
将所有非数字字符作为标记返回,因此新的语法规则就足够了
添加新运算符。但我们想要更灵活的东西:内置
其语法形式为以下形式的函数:yylex
function_name (argument)
同时,我们将通过允许您为计算器添加内存 若要创建命名变量,请在其中存储值,以便稍后使用它们。 以下是使用多功能计算器的示例会话:
$ mfcalc pi = 3.141592653589 ⇒ 3.1415926536
sin(pi) ⇒ 0.0000000000
alpha = beta1 = 2.3 ⇒ 2.3000000000 alpha ⇒ 2.3000000000 ln(alpha) ⇒ 0.8329091229 exp(ln(beta1)) ⇒ 2.3000000000 $
请注意,允许多个赋值和嵌套函数调用。
下一篇: mfcalc
语法规则, 上一篇: 多功能计算器: mfcalc
[内容][索引]
mfcalc
以下是多功能的 C 和 Bison 声明 计算器。
%{ #include <stdio.h> /* For printf, etc. */ #include <math.h> /* For pow, used in the grammar. */ #include "calc.h" /* Contains definition of 'symrec'. */ int yylex (void); void yyerror (char const *); %}
%define api.value.type union /* Generate YYSTYPE from these types: */ %token <double> NUM /* Double precision number. */ %token <symrec*> VAR FUN /* Symbol table pointer: variable/function. */ %nterm <double> exp
%precedence '=' %left '-' '+' %left '*' '/' %precedence NEG /* negation--unary minus */ %right '^' /* exponentiation */
上面的语法只介绍了Bison语言的两个新功能。 这些功能允许语义值具有各种数据类型 (请参阅多个值类型)。
分配给变量的特殊值指定使用符号的数据定义符号
类型。Bison 将生成 to 的适当定义
存储这些值。union
%define
api.value.type
YYSTYPE
由于值现在可以具有各种类型,因此必须关联类型
使用其语义值的每个语法符号。这些符号是 、 、 和 。他们的声明是
使用其数据类型(放置在尖括号之间)进行扩充。为
实例中,则 的值存储在 中。NUM
VAR
FUN
exp
NUM
double
Bison 构造用于声明非终端符号,
就像用于声明令牌种类一样。以前我们做过
之前不使用,因为非终端符号通常
由定义它们的规则隐式声明。但必须
显式声明,以便我们可以指定其值类型。请参阅非终端符号。%nterm
%token
%nterm
exp
下一篇: mfcalc
符号表, 上一篇: mfcalc
声明, 上一篇: 多功能计算器: mfcalc
[内容][索引]]
mfcalc
以下是多功能计算器的语法规则。
它们中的大多数是直接从 ;三条规则,
那些提到 或 的,是新的。calc
VAR
FUN
%% /* The grammar follows. */
input: %empty | input line ;
line: '\n' | exp '\n' { printf ("%.10g\n", $1); } | error '\n' { yyerrok; } ;
exp: NUM | VAR { $$ = $1->value.var; } | VAR '=' exp { $$ = $3; $1->value.var = $3; } | FUN '(' exp ')' { $$ = $1->value.fun ($3); } | exp '+' exp { $$ = $1 + $3; } | exp '-' exp { $$ = $1 - $3; } | exp '*' exp { $$ = $1 * $3; } | exp '/' exp { $$ = $1 / $3; } | '-' exp %prec NEG { $$ = -$2; } | exp '^' exp { $$ = pow ($1, $3); } | '(' exp ')' { $$ = $2; } ;
/* End of grammar. */ %%
下一篇: mfcalc
Lexer, 上一篇: mfcalc
语法规则, 上一篇: 多功能计算器: mfcalc
[内容][索引]]
mfcalc
多功能计算器需要一个符号表来跟踪 变量和函数的名称和含义。这不会影响 语法规则(动作除外)或野牛声明,但它 需要一些额外的 C 函数来支持。
符号表本身由记录链表组成。其 定义,保留在标题中,如下所示。它 提供要放置在表中的函数或变量。calc.h
/* Function type. */ typedef double (func_t) (double);
/* Data type for links in the chain of symbols. */ struct symrec { char *name; /* name of symbol */ int type; /* type of symbol: either VAR or FUN */ union { double var; /* value of a VAR */ func_t *fun; /* value of a FUN */ } value; struct symrec *next; /* link field */ };
typedef struct symrec symrec; /* The symbol table: a chain of 'struct symrec'. */ extern symrec *sym_table; symrec *putsym (char const *name, int sym_type); symrec *getsym (char const *name);
新版本的 will 调用来初始化
符号表:main
init_table
struct init { char const *name; func_t *fun; };
struct init const funs[] = { { "atan", atan }, { "cos", cos }, { "exp", exp }, { "ln", log }, { "sin", sin }, { "sqrt", sqrt }, { 0, 0 }, };
/* The symbol table: a chain of 'struct symrec'. */ symrec *sym_table;
/* Put functions in table. */ static void init_table (void)
{ for (int i = 0; funs[i].name; i++) { symrec *ptr = putsym (funs[i].name, FUN); ptr->value.fun = funs[i].fun; } }
只需编辑初始化列表并添加必要的包含 文件,您可以向计算器添加其他函数。
两个重要功能允许在
符号表。向函数传递名称和种类
( 或 ) 的对象。对象是
链接到列表的前面,并返回指向该对象的指针。
该函数将传递要查找的符号的名称。如果
找到,则返回指向该符号的指针;否则返回零。putsym
VAR
FUN
getsym
/* The mfcalc code assumes that malloc and realloc always succeed, and that integer calculations never overflow. Production-quality code should not make these assumptions. */ #include <assert.h> #include <stdlib.h> /* malloc, realloc. */ #include <string.h> /* strlen. */
symrec * putsym (char const *name, int sym_type) { symrec *res = (symrec *) malloc (sizeof (symrec)); res->name = strdup (name); res->type = sym_type; res->value.var = 0; /* Set value to 0 even if fun. */ res->next = sym_table; sym_table = res; return res; }
symrec * getsym (char const *name) { for (symrec *p = sym_table; p; p = p->next) if (strcmp (p->name, name) == 0) return p; return NULL; }
下一篇: mfcalc
Main, Previous: mfcalc
符号表, 上一篇: 多功能计算器: mfcalc
[内容][索引]]
mfcalc
该函数现在必须识别变量、数值和
单字符算术运算符。字母数字字符串
带有前导字母的字符被识别为变量或
函数取决于符号表对它们的说明。yylex
字符串将传递给符号表中的查找。如果
该名称将显示在表中,指针指向其位置和类型
( 或 ) 返回到 。如果不是
已经在表中,则将其作为 using .同样,指针及其类型(必须是 )是
返回 .getsym
VAR
FUN
yyparse
VAR
putsym
VAR
yyparse
在处理数值和算术时不需要更改
中的运算符。yylex
#include <ctype.h> #include <stddef.h>
int yylex (void) { int c = getchar (); /* Ignore white space, get first nonwhite character. */ while (c == ' ' || c == '\t') c = getchar (); if (c == EOF) return YYEOF;
/* Char starts a number => parse the number. */ if (c == '.' || isdigit (c)) { ungetc (c, stdin); if (scanf ("%lf", &yylval.NUM) != 1) abort (); return NUM; }
Bison 生成了一个定义,其中有一个名为存储符号值的成员。YYSTYPE
NUM
NUM
/* Char starts an identifier => read the name. */ if (isalpha (c)) { static ptrdiff_t bufsize = 0; static char *symbuf = 0;
ptrdiff_t i = 0; do
{ /* If buffer is full, make it bigger. */ if (bufsize <= i) { bufsize = 2 * bufsize + 40; symbuf = realloc (symbuf, (size_t) bufsize); } /* Add this character to the buffer. */ symbuf[i++] = (char) c; /* Get another character. */ c = getchar (); }
while (isalnum (c)); ungetc (c, stdin); symbuf[i] = '\0';
symrec *s = getsym (symbuf); if (!s) s = putsym (symbuf, VAR); yylval.VAR = s; /* or yylval.FUN = s. */ return s->type; } /* Any other character is a token by itself. */ return c; }
上一篇: mfcalc
Lexer, Up: Multi-Function Calculator: mfcalc
[Contents][Index]
mfcalc
错误报告功能保持不变,新版本包括对用户需求的调用和设置(有关详细信息,请参阅跟踪解析器):main
init_table
yydebug
/* Called by yyparse on error. */ void yyerror (char const *s) { fprintf (stderr, "%s\n", s); }
int main (int argc, char const* argv[])
{ /* Enable parse traces on option -p. */ if (argc == 2 && strcmp(argv[1], "-p") == 0) yydebug = 1;
init_table (); return yyparse (); }
该程序既强大又灵活。您可以轻松添加新的
函数,修改此代码进行安装是一项简单的工作
预定义的变量,例如 OR 以及。pi
e
上一篇: 多功能计算器: mfcalc
, 上一篇: 示例 [内容][索引]
init_table
VAR
Bison 将上下文无关的语法规范作为输入,并生成一个 C-language 函数,用于识别语法的正确实例。
Bison 语法文件通常具有以 '' 结尾的名称。 请参阅调用 Bison。.y
下一篇: 符号, 终端和非终端, 上一篇: 野牛语法文件 [内容][索引]
Bison 语法文件有四个主要部分,此处显示为 适当的分隔符:
%{ Prologue %} Bison declarations %% Grammar rules %% Epilogue
“”中括起来的注释可能出现在任何部分中。 作为 GNU 扩展,'' 引入了一个持续到结束的注释 的行。/* … *///
该部分包含宏定义和
在语法规则中的操作中使用的函数和变量。
这些被复制到解析器实现文件的开头,以便
它们先于 . 的定义之前。您可以使用“'
从头文件中获取声明。如果你不需要任何 C
声明,您可以省略 '' 和 '' 分隔符
将此部分括起来。Prologueyyparse
#include%{%}
该部分因首次出现 “'”位于注释、字符串文本或字符之外 不断。Prologue%}
您可能有多个部分,与 .这允许您拥有 C 和 Bison 声明
相互指代。例如,声明可以
使用头文件中定义的类型,您可能希望对函数进行原型设计
采用类型的参数。这可以通过两个块来完成,一个在声明之前,一个在声明之后。PrologueBison declarations%union
YYSTYPE
Prologue%union
%{ #define _GNU_SOURCE #include <stdio.h> #include "ptypes.h" %}
%union {
long n;
tree t; /* tree
is defined in ptypes.h. */
}
%{ static void print_token (yytoken_kind_t token, YYSTYPE val); %}
…
当有疑问时,通常将序幕代码放在所有 Bison 之前会更安全
声明,而不是之后。例如,要素的任何定义
测试宏,例如或应该出现
在所有 Bison 声明之前,因为特征测试宏会影响
Bison 生成的指令的行为。_GNU_SOURCE
_POSIX_C_SOURCE
#include
部分的功能通常可以是微妙的,并且
死板。作为替代方案,Bison 提供了一个指令
显式限定符字段,用于标识代码的用途和
因此,Bison 应该生成它的位置。对于 C/C++,
默认位置可以省略限定符,也可以是 、 、 之一。请参见%code 摘要。Prologue%code
requires
provides
top
再看一下上一节的示例:
%{ #define _GNU_SOURCE #include <stdio.h> #include "ptypes.h" %}
%union {
long n;
tree t; /* tree
is defined in ptypes.h. */
}
%{ static void print_token (yytoken_kind_t token, YYSTYPE val); %}
…
请注意,这里有两个部分,但有一个微妙的部分
它们的功能之间的区别。例如,如果您决定
覆盖 Bison 的默认定义 ,您应该在哪个部分编写新的
定义?5 你应该
首先编写它,因为 Bison 会将该代码插入解析器
实现文件之前的默认定义。在
您应该在哪个部分构建一个内部函数的原型,该函数接受 和 作为
参数?您应该在第二个中对其进行原型设计,因为 Bison 将插入
该代码在 和 定义之后。PrologueYYLTYPE
PrologueYYLTYPE
Prologuetrace_token
YYLTYPE
yytoken_kind_t
YYLTYPE
yytoken_kind_t
这两个部分在功能上的区别是
由他们之间的外观建立。这
行为引发了一些问题。首先,为什么 a 的位置会影响与 和 相关的定义?第二,如果没有呢?在那
情况下,第二种部分不可用。这
行为并不直观。Prologue%union
%union
YYLTYPE
yytoken_kind_t
%union
Prologue
为了避免这种微妙的依赖关系,请使用 a 和 unqualified 重写示例。让我们继续添加
新定义和原型
同时:%union
%code top
%code
YYLTYPE
trace_token
%code top { #define _GNU_SOURCE #include <stdio.h> /* WARNING: The following code really belongs * in a '%code requires'; see below. */ #include "ptypes.h" #define YYLTYPE YYLTYPE typedef struct YYLTYPE { int first_line; int first_column; int last_line; int last_column; char *filename; } YYLTYPE; }
%union {
long n;
tree t; /* tree
is defined in ptypes.h. */
}
%code { static void print_token (yytoken_kind_t token, YYSTYPE val); static void trace_token (yytoken_kind_t token, YYLTYPE loc); }
…
这样,不合格的人就实现了
与这两种部分的功能相同,但它是
始终明确您打算哪种类型。而且,这两种总是
即使在没有 .%code top
%code
Prologue%union
上面的块在逻辑上包含两部分。第一个
警告前的两行需要出现在解析器顶部附近
实现文件。警告后的第一行是必需的,因此也需要出现在解析器实现中
文件。但是,如果您已指示 Bison 生成解析器头文件
(参见 Bison Declaration Summary),您可能希望出现该行
在该头文件中的定义之前也是如此。该定义还应出现在解析器头文件中,以
覆盖那里的默认定义。%code top
YYSTYPE
YYSTYPE
YYLTYPE
YYLTYPE
换句话说,在上面的块中,除了前两个之外
行是 AND 定义所需的依赖项代码。
因此,它们属于一个或多个:%code top
YYSTYPE
YYLTYPE
%code requires
%code top { #define _GNU_SOURCE #include <stdio.h> }
%code requires { #include "ptypes.h" }
%union {
long n;
tree t; /* tree
is defined in ptypes.h. */
}
%code requires { #define YYLTYPE YYLTYPE typedef struct YYLTYPE { int first_line; int first_column; int last_line; int last_column; char *filename; } YYLTYPE; }
%code { static void print_token (yytoken_kind_t token, YYSTYPE val); static void trace_token (yytoken_kind_t token, YYLTYPE loc); }
…
现在,Bison 将在解析器实现文件和解析器标头的 Bison 生成和定义之前插入新定义
文件。(按照同样的推理,也会是
适合编写自己的定义。#include "ptypes.h"
YYLTYPE
YYSTYPE
YYLTYPE
%code requires
YYSTYPE
当你为 和 编写依赖项代码时,
无论如何,您都应该更喜欢
是否指示 Bison 生成解析器头文件。当你是
编写仅需要 Bison 插入解析器的代码
实现文件,并且没有特殊需要出现在
该文件,您应该更喜欢不合格的 .这些做法将使代码的每个块的目的
对 Bison 和其他读取您的语法文件的开发人员来说是明确的。
按照这些做法,我们期望不合格的,并且是四种备选方案中最重要的。YYSTYPE
YYLTYPE
%code requires
%code top
%code
%code
top
%code
%code requires
Prologue
在开发解析器时的某个时刻,您可能会决定向解析器外部的模块提供。因此,您
可能希望 Bison 将原型插入到两个解析器标头中
文件和解析器实现文件。由于此函数不是
or 需要的依赖关系,它不会使
感觉将其原型移动到 .更重要的是,
因为它依赖于 和 是不够的。相反,将其原型从
不合格:trace_token
YYSTYPE
YYLTYPE
%code requires
YYLTYPE
yytoken_kind_t
%code
requires
%code
%code provides
%code top { #define _GNU_SOURCE #include <stdio.h> }
%code requires { #include "ptypes.h" }
%union {
long n;
tree t; /* tree
is defined in ptypes.h. */
}
%code requires { #define YYLTYPE YYLTYPE typedef struct YYLTYPE { int first_line; int first_column; int last_line; int last_column; char *filename; } YYLTYPE; }
%code provides { void trace_token (yytoken_kind_t token, YYLTYPE loc); }
%code { static void print_token (FILE *file, int token, YYSTYPE val); }
…
Bison 会将原型插入到两个解析器中
头文件和 、 和 的定义之后的解析器实现文件。trace_token
yytoken_kind_t
YYLTYPE
YYSTYPE
上面的示例小心翼翼地按照以下顺序编写指令:
生成的解析器实现和头文件的布局:、 、 和 。虽然您的语法文件通常可能更容易阅读,但如果
你也遵循这个顺序,野牛不需要它。取而代之的是,Bison 让
你选择一个对你有意义的组织。%code top
%code requires
%code provides
%code
您可以在语法文件中多次声明这些指令中的任何一个。 在这种情况下,Bison 按声明顺序连接包含的代码。 这是这些指令之一的位置在 语法文件会影响其功能。
前两个属性的结果是,您可以更灵活地处理 整理语法文件。 例如,您可以按语义组织与语义类型相关的指令 类型:
%code requires { #include "type1.h" } %union { type1 field1; } %destructor { type1_free ($$); } <field1> %printer { type1_print (yyo, $$); } <field1>
%code requires { #include "type2.h" } %union { type2 field2; } %destructor { type2_free ($$); } <field2> %printer { type2_print (yyo, $$); } <field2>
您甚至可以将上述每个指令组放在
使用关联语义的规则集旁边的语法文件
类型。
(在规则部分中,您必须用
分号。
而且您不必担心
定义部分将对其功能产生不利影响
违反直觉的方式,只是因为它是第一位的。
使用部分是不可能的。%union
Prologue
本节关注的是解释四种备选方案相对于原始 Yacc 的优势。
但是,在大多数情况下,使用这些指令时,您不需要
考虑一下这里讨论的所有低级排序问题。
相反,您应该简单地使用这些指令来标记
根据其目的进行编码,并让 Bison 处理排序。 是最通用的标签。
根据需要将代码移动到 、 或 。ProloguePrologue%code
%code requires
%code provides
%code top
语法规则部分包含一个或多个 Bison 语法 规则,仅此而已。请参阅语法规则。
必须始终至少有一个语法规则,并且第一个 ''(在语法规则之前)甚至永远不会被省略 如果它是文件中的第一件事。%%
被逐字复制到解析器的末尾
实现文件,就像复制到
开始。这是放置任何东西的最方便的地方
希望在解析器实现文件中拥有,但不需要
在定义之前。例如,定义
的,经常去这里。因为 C 需要
函数在使用前要声明,通常需要声明
功能类似于序幕,甚至
如果您在结语中定义它们。请参阅分析器 C 语言接口。EpiloguePrologueyyparse
yylex
yyerror
yylex
yyerror
如果最后一部分是空的,您可以省略分隔它的“” 从语法规则。%%
Bison 解析器本身包含许多宏和标识符,其名称 以“”或“”开头,因此最好避免使用 结语中的任何此类名称(本手册中记录的名称除外) 语法文件。yyYY
Bison 语法中的符号表示语法分类 的语言。
终端符号(也称为令牌类型)表示
语法等效标记的类。在语法中使用符号
规则表示允许该类中的令牌。符号是
在 Bison 解析器中由数字代码表示,该函数返回一个令牌类型代码来指示已是哪种令牌
读。你不需要知道代码值是什么;您可以使用符号
代表它。yylex
非终端符号在语法上代表一类 等效分组。符号名称用于编写语法规则。 按照惯例,它应该全部是小写的。
符号名称可以包含字母、下划线、句点和非首字母 数字和破折号。符号名称中的破折号是 GNU 扩展名,不兼容 与 POSIX Yacc 合作。句点和破折号使符号名称不太方便 与命名引用一起使用,这些引用需要在此类名称周围加上括号 (请参阅命名引用)。包含句点或破折号的终端符号 没什么意义:因为它们不是有效的符号(在大多数编程中 languages),它们不会导出为令牌名称。
语法中有三种写终端符号的方法:
%token
'+'
按照约定,字符标记类型仅用于表示以下标记:
由该特定字符组成。因此,令牌种类为
用于表示字符“”作为标记。没有什么可以强制执行这一点
约定,但如果你偏离它,你的程序会混淆其他
读者。'+'
+
可以使用 C 中字符文字中使用的所有常用转义序列
在 Bison 中也是如此,但您不得将 null 字符用作字符
字面意思,因为它的数字代码 0 表示输入结束
(请参阅 yylex
的调用约定)。此外,与标准 C 不同,三元组没有
野牛字符文字中的特殊含义,反斜杠换行符也不是
允许。
"<="
您可以将文本字符串标记与符号名称关联为别名,
使用声明(请参阅令牌种类名称)。如果你不这样做
也就是说,词法分析器必须检索文本的标记代码
string token (请参阅 yylex
的调用约定)。%token
yytname
警告:文字字符串标记在 Yacc 中不起作用。
按照惯例,文本字符串标记仅用于表示标记
它由该特定字符串组成。因此,您应该使用令牌
kind 表示字符串 '' 作为标记。野牛
不强制执行此约定,但如果您偏离它,则
阅读你的程序会感到困惑。"<="
<=
C 中字符串文字中使用的所有转义序列都可以在 Bison 也是如此,但您不能在 字符串文本。此外,与标准 C 不同,三元组没有特殊性 在 Bison 字符串文字中表示,也不允许使用反斜杠换行符。一个 文本字符串标记必须包含两个或更多字符;对于令牌 仅包含一个字符,请使用字符标记(见上文)。
您选择如何编写终端符号对其没有影响 语法意义。这仅取决于它在规则中出现的位置,并且 当解析器函数返回该符号时。
返回的值始终是终端之一
符号,但零值或负值表示输入结束。
无论您在语法规则中以哪种方式编写标记类型,您都编写
在 的定义中也是如此。数字代码
对于字符,令牌种类只是
字符,因此可以使用相同的值来生成
必要的代码,但您可能需要将其转换为以避免在签名的主机上进行签名扩展。
每个命名的标记类型都成为解析器实现中的 C 宏
文件,所以可以使用名称来代表代码。(这
这就是为什么句点在终端符号中没有意义的原因。请参阅 yylex
的调用约定。yylex
yylex
yylex
unsigned
char
char
yylex
如果在单独的文件中定义,则需要安排
令牌种类定义在那里可用。使用该选项
当您运行 Bison 时,它会将这些定义写入单独的
头文件,您可以将其包含在另一个文件中
需要它的源文件。请参阅调用 Bison。yylex
-dname.tab.h
如果你想编写一个可以移植到任何标准 C 的语法 host,则只能使用从基本 标准 C 的执行字符集。这套由十个组成 digits、52 个小写和大写英文字母,以及 以下 C 语言字符串中的字符:
"\a\b\t\n\v\f\r !\"#%&'()*+,-./:;<=>?[\\]^_{|}~"
函数和 Bison 必须使用一致的字符集
以及字符标记的编码。例如,如果您在
ASCII 环境,然后编译并运行生成的
在使用不兼容字符集的环境中编程,例如
EBCDIC,生成的程序可能无法正常工作,因为表
由 Bison 生成的 ASCII 数值将假定为
字符令牌。软件发行版的标准做法是
包含 Bison 在
ASCII 环境,因此安装程序在以下平台上
与 ASCII 不兼容,必须先重建这些文件
编译它们。yylex
该符号是为错误恢复保留的终端符号
(请参阅错误恢复);您不应将其用于任何其他目的。
特别是,不应返回此值。默认值
错误标记的值为 256,除非将 256 显式分配给
带有声明的令牌之一。error
yylex
%token
Bison 语法是规则列表。
Bison 语法规则具有以下一般形式:
result: components…;
其中 是此规则描述的非终端符号, 并且是各种终端和非终端符号 由此规则组合在一起(请参阅符号、终端和非终端)。resultcomponents
例如
exp: exp '+' exp;
表示两个类型的分组,中间有一个 '' 标记,
可以组合成一个更大的类型分组。exp
+exp
规则中的空白仅对分隔符号有意义。您可以添加 随心所欲地增加空白。
分散在组件之间可以确定 规则的语义。操作如下所示:actions
{C statements}
这是支撑代码的一个示例,即 C 代码被 大括号,很像 C 语言中的复合语句。 任何 C 标记序列,只要其大括号是平衡的。野牛 不直接检查支撑代码的正确性;它只是 将代码复制到解析器实现文件,其中 C 编译器可以检查它。
在大括号代码中,平衡大括号计数不受大括号的影响 在注释、字符串文本或字符常量中,但它是 受 C 二元字母 '' 和 '' 的影响,它们表示 括号。在顶层,支撑代码必须以 '' 结尾 而不是通过二分法。野牛不寻找三元组,所以如果支撑 代码使用三元图,应确保它们不会影响 大括号的嵌套或注释的边界、字符串文字或 字符常量。<%%>}
通常只有一个动作,它遵循组件。 请参阅操作。
同一的多个规则可以单独编写,也可以 与竖线字符 '' 连接,如下所示:result|
result: rule1-components… | rule2-components… … ;
即使以这种方式连接,它们仍然被认为是不同的规则。
如果规则的右侧 () 为空。这意味着在前面的示例中可以匹配 空字符串。再举一个例子,下面是如何定义一个可选的 分号:componentsresult
semicolon.opt: | ";";
很容易看不到空规则,尤其是在使用时。该指令允许明确规则为
目的:|
%empty
semicolon.opt: %empty | ";" ;
标记非空规则是错误的。如果使用 运行,则将报告没有 的空规则。除非指定,否则使用将启用此警告。%empty
-Wempty-rulebison
%empty
%empty
-Wno-empty-rule
该指令是 Bison 扩展,它不适用于
雅克。为了保持与 POSIX Yacc 的兼容性,习惯上将
注释 '' 在每个没有组件的规则中:%empty
/* empty */
semicolon.opt: /* empty */ | ";" ;
当规则的非终端时,规则称为递归规则 也出现在它的右手边。几乎所有的 Bison 语法都需要 使用递归,因为这是定义任何 特定事物的数字。考虑这个递归定义 一个或多个表达式的逗号分隔序列:result
expseq1: exp | expseq1 ',' exp ;
由于递归使用 是 中最左边的符号
右手边,我们称之为左递归。相比之下,这里
使用右递归定义相同的构造:expseq1
expseq1: exp | exp ',' expseq1 ;
任何类型的序列都可以使用左递归或右递归来定义 递归,但你应该始终使用左递归,因为它可以 解析具有有限堆栈空间的任意数量元素的序列。 右递归占用 Bison 堆栈上的空间与 序列中的元素数,因为所有元素都必须 在规则应用一次之前就转移到堆栈上。 有关进一步的解释,请参阅 Bison 解析器算法 这个。
间接递归或相互递归发生在以下结果 规则不会直接出现在其右侧,但确实会出现 在右侧出现的其他非终端的规则中 边。
例如:
expr: primary | primary '+' primary ;
primary: constant | '(' expr ')' ;
定义了两个相互递归的非终端,因为每个都引用 其他。
语言的语法规则仅决定语法。语义 由与各种标记关联的语义值决定,并且 分组,以及识别各种分组时采取的行动。
例如,计算器计算正确,因为值 与每个表达式关联的是正确的数字;它正确添加 因为分组“”的操作是添加 与 和 关联的数字。x + yxy
在一个简单的程序中,使用相同的数据类型可能就足够了 所有语言结构的语义值。在 RPN 和中缀计算器示例(请参阅反向波兰符号计算器)。
Bison 通常使用该类型作为语义值,如果您的程序
对所有语言构造使用相同的数据类型。指定一些其他
类型,如下定义变量:int
%define
api.value.type
%define api.value.type {double}
或
%define api.value.type {struct semantic_value_type}
的值应为不
包含括号或方括号。api.value.type
或者,在 C 语言中,不要依赖 Bison 的支持,
您可以依赖 C 预处理器并定义为宏:%define
YYSTYPE
#define YYSTYPE double
此宏定义必须放在语法文件的序言中
(见野牛语法大纲)。如果与 POSIX Yacc 的兼容性对您很重要,
使用这个。但请注意,Bison 无法知道 的值,而不是
即使它是否被定义,所以它无法提供服务。
此外,这仅适用于 C。YYSTYPE
在大多数程序中,不同类型的数据需要不同的数据类型
的令牌和分组。例如,数值常量可能需要 type or ,而字符串常量需要 type ,标识符可能需要指向
符号表。int
long
char *
要在一个解析器中对语义值使用多种数据类型,Bison 要求你做两件事:
%union
);
%define
api.value.type
);
typedef
#define
YYSTYPE
%token
%nterm
%type
变量的特殊值指示 Bison 类型标签(与 和 指令一起使用)是真正的类型,
不是 的成员姓名。union
%define
api.value.type
%token
%nterm
%type
YYSTYPE
例如:
%define api.value.type union %token <int> INT "integer" %token <int> 'n' %nterm <int> expr %token <char const *> ID "identifier"
生成适当的值以支持每个交易品种
类型。令牌的成员名称比具有
声明的标识符(例如 和 以上,但
不是 ) 是 。其他符号未指定
您不应该依赖的名称;而是依靠 C 强制转换来访问
具有适当类型的语义值:YYSTYPE
YYSTYPE
idINT
ID
'n'
id
/* For an "integer". */ yylval.INT = 42; return INT; /* For an 'n', also declared as int. */ *((int*)&yylval) = 42; return 'n'; /* For an "identifier". */ yylval.ID = "42"; return ID;
如果定义了变量
(参见 %define Summary),则它也用于前缀
工会成员姓名。例如,使用 '':%define
api.token.prefix
%define api.token.prefix
{TOK_}
/* For an "integer". */ yylval.TOK_INT = 42; return TOK_INT;
如果启用了 (或 /),则此 Bison 扩展无法工作,因为 POSIX 要求 Yacc
将标记生成为宏(例如,“'”或“'”)。%yacc
-y--yacc#define INT 258#define
TOK_INT 258
为 C++ 提供了类似的功能,该功能还克服了 C++
限制(禁止非平凡对象成为 A 的一部分):
'',请参阅 C++ 变体。union
%define api.value.type variant
下一篇: 提供结构化语义值类型, 上一篇: 生成语义值类型, 上一篇: 定义语言语义 [内容][索引]
该声明指定了可能的整个集合
语义值的数据类型。关键字后跟
支撑代码包含与 C 中 a 相同的内容。%union
%union
union
例如:
%union { double val; symrec *tptr; }
这表示两种替代类型是 和 。他们被赋予了名字和 ;使用这些名称
在 和 声明中选择
终端符号或非终端符号的类型之一(请参阅非终端符号)。double
symrec
*
val
tptr
%token
%nterm
%type
作为 POSIX 的扩展,允许在 .为
例:%union
%union value { double val; symrec *tptr; }
指定 union 标签,因此对应的 C 类型为 。如果未指定标记,则默认为 (请参阅 %define Summary)。value
union value
YYSTYPE
作为 POSIX 的另一个扩展,您可以指定多个声明;它们的内容是串联的。但是,只有第一个声明可以指定标记。%union
%union
请注意,与在 C 中进行声明不同,您不需要编写
右大括号后的分号。union
如果您的语法至少包含一个 '' ,则可以定义和使用自己的联合类型,而不是
标记。例如,您可以将以下内容放入头文件中:%union
YYSTYPE
<type>parser.h
union YYSTYPE { double val; symrec *tptr; };
然后你的语法可以使用以下内容来代替:%union
%{ #include "parser.h" %} %define api.value.type {union YYSTYPE} %nterm <val> expr %token <tptr> ID
实际上,您也可以提供一个相当的,
如果您想跟踪每个符号的信息(例如
如前所述)。struct
union
您提供的类型甚至可以是结构化的,并包含指针,其中 案例 您提供的类型标签可能是复合的,带有 '' 和 '' 运营商。.->
下一篇: 动作中值的数据类型, 上一篇: 提供结构化的语义值类型, 上一篇: 定义语言语义 [内容][索引]
操作附带语法规则,并包含要执行的 C 代码 每次识别该规则的实例时。大多数操作的任务 是计算由规则构建的分组的语义值,从 与标记或较小分组关联的语义值。
操作由包含 C 语句的支撑代码组成,可以是 放置在规则中的任何位置; 它在该位置执行。大多数规则在 规则的末尾,跟随所有组件。中间的动作 规则很棘手,仅用于特殊目的(请参阅 Midrule 中的操作)。
动作中的 C 代码可以引用
规则与构造匹配的组件,
它代表第 th 个组件的值。语义
正在构造的分组的值为 。另外
符号的语义值可以通过命名
引用构造或 .
Bison 将这两种结构都转化为
将操作复制到分析器中的适当类型
实现文件。 (或者,当它站立时
对于当前分组)被转换为可修改的左值,因此它
可以分配给。$n
n$$
$name
$[name]
$$
$name
下面是一个典型的例子:
exp: … | exp '+' exp { $$ = $1 + $3; }
或者,就命名引用而言:
exp[result]: … | exp[left] '+' exp[right] { $result = $left + $right; }
此规则从两个较小的分组构造一个
通过加号令牌连接。在动作中,和 ( 和 )
参考两个组件分组的语义值,
这是规则右侧的第一个和第三个符号。
总和存储在 () 中,因此它变为
语义值
规则刚刚识别的加法表达式。如果有一个
与“”标记关联的有用语义值,它可以是
称为 .exp
exp
$1
$3
$left
$right
exp
$$
$result
+$2
有关使用命名引用的详细信息,请参阅命名引用 引用构造。
请注意,竖线字符“”实际上是一条规则 分隔符,操作附加到单个规则。这是一个 与 Flex 等工具的区别,其中 '' 代表任一 “或”或“与下一条规则相同的操作”。在 以下示例,仅当找到“”时才会触发该操作:||b
a-or-b: 'a'|'b' { a_or_b_found = 1; };
如果未为规则指定操作,则 Bison 会提供默认值:.因此,规则中第一个符号的值
成为整个规则的值。当然,默认操作是
仅当两种数据类型匹配时才有效。没有有意义的默认值
空规则的操作;每个空规则都必须有一个显式操作
除非规则的值无关紧要。$$ = $1
$n
允许使用零或负作为参考
到堆栈上的令牌和分组,然后与
当前规则。这是一种非常冒险的做法,并且要可靠地使用它
您必须确定应用规则的上下文。这里
是您可以可靠地使用它的情况:n
foo: expr bar '+' expr { … } | expr bar '-' expr { … } ;
bar: %empty { previous_expr = $0; } ;
只要仅以此处显示的方式使用,则始终指
的定义。bar
$0
expr
bar
foo
如果满足以下条件,还可以访问前瞻令牌的语义值
any,来自语义操作。
此语义值存储在 中。
请参阅在操作中使用的特殊功能。yylval
Next: Midrule 中的操作, Previous: Actions, Up: 定义语言语义 [Contents][Index]
如果为语义值选择了单一数据类型,则 and 构造始终具有该数据类型。$$
$n
如果您曾经指定过各种数据类型,那么
必须为每个终端或非终端声明这些类型中的选择
可以具有语义值的符号。然后,每次使用 or 时,其数据类型由它引用的符号决定
在规则中。在此示例中,%union
$$
$n
exp: … | exp '+' exp { $$ = $1 + $3; }
$1
并引用 的实例,所以它们都
为非终端符号声明数据类型。如果使用,它将为
终端符号,不管它是什么。$3
exp
exp
$2
'+'
或者,您可以在引用值时指定数据类型, 通过在开头的“”之后插入“” 参考。例如,如果已定义类型,如下所示:<type>$
%union { int itype; double dtype; }
然后你可以写来引用
规则为整数,或将其称为双精度。$<itype>1
$<dtype>1
有时,将操作放在规则的中间很有用。 这些操作的编写方式与通常的规则结束操作类似,但它们 在解析器识别以下组件之前执行。
Next: 键入的 Midrule 操作,向上:Midrule 中的操作 [内容][索引]
中间规则操作可以引用它前面的组件 using ,但它可能不引用后续组件,因为
它在解析它们之前运行。$n
中间规则操作本身算作规则的组成部分之一。
当同一规则中稍后有另一个操作时,这会有所不同
(通常最后还有另一个):你必须计算动作
以及计算要在 中使用哪个数字时的符号。n$n
midrule 操作也可以具有语义值。操作可以设置
其值赋值为 ,并在规则中稍后执行操作
可以使用 来引用该值。由于没有符号
若要命名操作,无法声明值的数据类型
提前,所以你必须使用 '' 构造来
每次引用此值时指定数据类型。$$
$n
$<…>n
无法使用中间规则设置整个规则的值
动作,因为赋值没有这种效果。这
设置整个规则值的唯一方法是使用普通操作
在规则的末尾。$$
下面是一个假设编译器的示例,它处理类似于 '' 和
用于创建一个临时命名的变量
的持续时间。要解析这个结构,我们必须在解析时放入符号表中,然后
之后将其删除。这是如何完成的:let
let (variable) statementvariablestatementvariablestatement
stmt: "let" '(' var ')' { $<context>$ = push_context (); declare_variable ($3); } stmt { $$ = $6; pop_context ($<context>5); }
一旦“”被识别,第一个
操作运行。它保存当前语义上下文的副本(
可访问变量列表)作为其语义值,在数据类型联合中使用 alternative。然后,它调用以将新变量添加到该列表中。一旦
第一个动作完成,嵌入语句可以
解析。let (variable)context
declare_variable
stmt
请注意,midrule 操作是组件编号 5,因此 '' 是 组件编号 6。命名引用可用于提高可读性 和可维护性(请参阅命名引用):stmt
stmt: "let" '(' var ')' { $<context>let = push_context (); declare_variable ($3); }[let] stmt { $$ = $6; pop_context ($<context>let); }
解析嵌入语句后,其语义值变为
整个 -statement 的值。然后语义值从
“先前操作”用于还原先前的变量列表。这
从列表中删除临时变量,使其不会
在解析程序的其余部分时似乎存在。let
let
因为 midrule 操作的语义值类型是未知的
Bison,基于类型的功能(例如,'','')可以
不起作用,这可能会导致内存泄漏。他们还禁止使用
在 C++
(请参阅 C++ 变体)。%printer%destructorvariant
api.value.type
有关解决此问题的一种方法,请参阅类型化中间规则操作,以及有关另一种方法:转换中间操作操作,请参阅类型化中间规则操作 变成常规行动。
下一篇: Midrule 动作翻译,上一篇:使用 Midrule 动作,上一篇:Midrule 中的动作 [内容][索引]
在上面的例子中,如果解析器在解析嵌入语句中的标记时启动了错误恢复(参见 Error Recovery),则
它可能会丢弃以前的语义上下文,而没有
恢复它。因此,需要一个析构函数
(参见释放丢弃的符号),而 Bison 需要
语义值 () 的类型来选择正确的析构函数。stmt
$<context>5
$<context>5
context
作为 Yacc 的 midrule 操作的扩展,Bison 提供了一种键入方法 它们的语义值:指定其 type 标记(中间线前的 '' 行动。<...>
请考虑前面的示例,其中包含一个非类型的 midrule 操作:
stmt: "let" '(' var ')' { $<context>$ = push_context (); // *** declare_variable ($3); } stmt { $$ = $6; pop_context ($<context>5); // *** }
相反,如果你写:
stmt: "let" '(' var ')' <context>{ // *** $$ = push_context (); // *** declare_variable ($3); } stmt { $$ = $6; pop_context ($5); // *** }
然后正常工作(不再有泄漏!
可以使用 C++ s,并减少冗余(指定一次)。%printer
%destructor
variant
<context>
下一篇: Midrule 操作导致的冲突, Previous: 键入的 Midrule 操作, Up: Midrule 中的操作 [内容][索引]]
中间规则操作实际上转换为常规规则和操作。 Bison 生成的各种报告(文本、图形等,参见 Understanding Your Parser)揭示了这个翻译, 最好通过一个例子来解释。以下规则:
exp: { a(); } "b" { c(); } { d(); } "e" { f(); };
被翻译成:
$@1: %empty { a(); }; $@2: %empty { c(); }; $@3: %empty { d(); }; exp: $@1 "b" $@2 $@3 "e" { f(); };
使用新的非终端符号,其中是一个数字。$@n
n
如果 midrule 操作使用 ,则应生成一个值,或者
(最终)操作使用 where 表示中间线
行动。在这种情况下,它的非终端被命名为:$$
$n
n@n
exp: { a(); } "b" { $$ = c(); } { d(); } "e" { f = $1; };
被翻译成
@1: %empty { a(); }; @2: %empty { $$ = c(); }; $@3: %empty { d(); }; exp: @1 "b" @2 $@3 "e" { f = $1; }
在上面的例子中可能有两个错误:第一个中间规则操作
不生成值(它不使用,虽然最终
action 使用它),并且不使用第二个的值(final
操作不使用 )。Bison在启用警告时会报告这些错误(请参阅调用Bison):$$
$3
midrule-value
$ bison -Wmidrule-value mid.y
mid.y:2.6-13: warning: unset value: $$ 2 | exp: { a(); } "b" { $$ = c(); } { d(); } "e" { f = $1; }; | ^~~~~~~~
mid.y:2.19-31: warning: unused value: $3 2 | exp: { a(); } "b" { $$ = c(); } { d(); } "e" { f = $1; }; | ^~~~~~~~~~~~~
有时将中间规则操作转换为常规操作很有用,例如, 考虑它们,或逃避它们的局限性。例如,作为 除了键入的 midrule 操作之外,您还可以隐藏 midrule 操作 在非终端符号内,并声明打印机和 析构函数 该符号:
%nterm <context> let %destructor { pop_context ($$); } let %printer { print_context (yyo, $$); } let
%%
stmt: let stmt { $$ = $2; pop_context ($let); };
let: "let" '(' var ')' { $let = push_context (); declare_variable ($var); };
上一篇: Midrule 动作翻译, Up: Actions in Midrule [Contents][Index]
在规则被完全识别之前采取行动通常会导致 冲突,因为解析器必须提交解析才能执行 行动。例如,以下两个规则,不带中间规则操作, 可以在工作解析器中共存,因为解析器可以移动大括号 令牌,并在决定是否存在 声明与否:
compound: '{' declarations statements '}' | '{' statements '}' ;
但是,当我们按如下方式添加 midrule 操作时,规则将变得不起作用:
compound: { prepare_for_local_variables (); } '{' declarations statements '}'
| '{' statements '}' ;
现在,解析器被迫决定是否运行 midrule 操作 当它读得不超过开括号时。换句话说,它 必须承诺使用一条或另一条规则,而没有足够的规则 正确执行此操作的信息。(大括号令牌就是所谓的 此时的 Lookahead 令牌,因为解析器仍在 决定如何处理它。请参阅 Lookahead 令牌。
您可能认为您可以通过将相同的 动作成两条规则,如下所示:
compound: { prepare_for_local_variables (); } '{' declarations statements '}' | { prepare_for_local_variables (); } '{' statements '}' ;
但这无济于事,因为野牛没有意识到这两个动作 是相同的。(Bison 从不尝试理解动作中的 C 代码。
如果语法使得声明可以与 语句由第一个标记(在 C 中为 true),然后是一个解决方案 确实有效是将动作放在大括号之后,如下所示:
compound: '{' { prepare_for_local_variables (); } declarations statements '}' | '{' statements '}' ;
现在是以下声明或语句的第一个标记, 无论如何,它都会告诉 Bison 使用哪条规则,仍然可以这样做。
另一种解决方案是将操作隐藏在非终端符号中,该符号 用作子例程:
subroutine: %empty { prepare_for_local_variables (); } ;
compound: subroutine '{' declarations statements '}' | subroutine '{' statements '}' ;
现在 Bison 可以在没有的情况下执行规则中的操作
决定最终将使用哪个规则。subroutine
compound
虽然语法规则和语义操作足以写出一个完整的 函数式解析器,处理一些附加信息可能很有用, 尤其是符号位置。
位置的处理方式是通过提供数据类型来定义的,并且 规则匹配时要执行的操作。
为位置定义数据类型比为语义值定义数据类型要简单得多。 因为所有标记和分组始终使用相同的类型。位置类型 使用 '':%define api.location.type
%define api.location.type {location_t}
这在 C 生成的代码中定义了类型名称。未定义时,Bison 使用具有四个的默认结构类型
成员:YYLTYPE
YYLTYPE
typedef struct YYLTYPE { int first_line; int first_column; int last_line; int last_column; } YYLTYPE;
在 C 中,您还可以通过定义一个名为 的宏来指定位置类型,就像您可以通过定义
宏(请参阅语义值的数据类型)。但是,而不是使用
宏,我们推荐 AND 变量。YYLTYPE
YYSTYPE
api.value.type
api.location.type
%define
默认位置表示源文件中的一个范围,但这不是一个 要求。它可以是一个点,也可以只是一个行号,甚至更多 结构复杂。
使用默认位置类型时,Bison 将初始化所有这些字段
在解析开始时设置为 1。使用自定义位置类型进行初始化(或选择其他位置类型
initialization),使用指令。请参阅在解析之前执行操作。yylloc
yylloc
%initial-action
操作不仅对定义语言语义有用,而且对 使用位置描述输出解析器的行为。
构建句法分组位置的最明显方法是非常
类似于语义值的计算方式。在给定的规则中,几个
构造可用于访问正在匹配的元素的位置。
右侧的第 个分量的位置是 ,而左侧分组的位置是 。n@n
@$
此外,命名引用构造符号位置,也可用于寻址符号位置。
有关使用命名引用的详细信息,请参阅命名引用
引用构造。@name
@[name]
下面是使用位置默认数据类型的基本示例:
exp: … | exp '/' exp { @$.first_column = @1.first_column; @$.first_line = @1.first_line; @$.last_column = @3.last_column; @$.last_line = @3.last_line; if ($3) $$ = $1 / $3; else { $$ = 1; fprintf (stderr, "%d.%d-%d.%d: division by zero", @3.first_line, @3.first_column, @3.last_line, @3.last_column); } }
至于语义值,位置有一个默认操作,即
每次匹配规则时运行。它将 的开头设置为
第一个符号的开头,以及 的结尾
最后一个符号。@$
@$
使用此默认操作,位置跟踪可以全自动。这 上面的例子只是这样重写:
exp: … | exp '/' exp { if ($3) $$ = $1 / $3; else { $$ = 1; fprintf (stderr, "%d.%d-%d.%d: division by zero", @3.first_line, @3.first_column, @3.last_line, @3.last_column); } }
也可以访问前瞻令牌的位置(如果有),
从语义操作。
此位置存储在 中。
请参阅在操作中使用的特殊功能。yylloc
使用默认位置类型时,调试跟踪报告符号的
位置。生成的解析器使用宏执行此操作。YYLOCATION_PRINT
);
¶启用跟踪后,在(类型为“”)上打印(类型为“”)。禁用跟踪时不执行任何操作,或者 如果位置类型是用户定义的。locYYLTYPE const *fileFILE *
要使用用户定义的位置类型获取调试跟踪中的位置,
定义宏。例如:YYLOCATION_PRINT
#define YYLOCATION_PRINT location_print
实际上,操作并不是计算位置的最佳位置。因为
位置比语义值要普遍得多,在
输出解析器,用于重新定义要为每个执行的默认操作
统治。每次规则
匹配,然后运行关联的操作。它也被调用
在处理语法错误时,计算错误的位置。
在报告无法解决的句法歧义之前,GLR
解析器以递归方式调用以计算位置
这种模棱两可。YYLLOC_DEFAULT
YYLLOC_DEFAULT
大多数情况下,此宏足够通用,可以抑制位置 来自语义操作的专用代码。
宏采用三个参数。第一个是
分组的位置(计算结果)。当一个
规则匹配,则第二个参数标识
规则的所有右侧元素都匹配,以及第三个
参数是规则右侧的大小。
当 GLR 解析器报告歧义时,哪个候选者
它传递到的右侧未定义。
处理语法错误时,第二个参数标识位置
在错误处理过程中丢弃的符号,以及第三个
参数是丢弃的符号数。YYLLOC_DEFAULT
YYLLOC_DEFAULT
默认情况下,是这样定义的:YYLLOC_DEFAULT
# define YYLLOC_DEFAULT(Cur, Rhs, N) \ do \ if (N) \ { \ (Cur).first_line = YYRHSLOC(Rhs, 1).first_line; \ (Cur).first_column = YYRHSLOC(Rhs, 1).first_column; \ (Cur).last_line = YYRHSLOC(Rhs, N).last_line; \ (Cur).last_column = YYRHSLOC(Rhs, N).last_column; \ } \ else \ { \ (Cur).first_line = (Cur).last_line = \ YYRHSLOC(Rhs, 0).last_line; \ (Cur).first_column = (Cur).last_column = \ YYRHSLOC(Rhs, 0).last_column; \ } \ while (0)
其中 是第 个符号的位置
in when 为正数,以及符号的位置
就在还原之前,当 和 都为零。YYRHSLOC (rhs, k)
krhskkn
在定义时,应考虑:YYLLOC_DEFAULT
YYLLOC_DEFAULT
如前面各节所述,引用任何
语义值或位置是一个位置引用,它采用
形式 、 、 和 。然而
这样的参考不是很具有描述性。此外,如果您以后决定
在语法规则的右侧插入或删除符号,需要
重新编号此类引用可能很乏味且容易出错。$n
$$
@n
@$
为了避免这些问题,您还可以引用语义值或位置 使用命名引用。首先,原始符号名称可以是 用作命名引用。例如:
invocation: op '(' args ')' { $invocation = new_invocation ($op, $args, @invocation); }
位置引用和命名引用可以任意混合使用。例如:
invocation: op '(' args ')' { $$ = new_invocation ($op, $args, @$); }
但是,有时常规符号名称是不够的,因为 歧义:
exp: exp '/' exp { $exp = $exp / $exp; } // $exp is ambiguous. exp: exp '/' exp { $$ = $1 / $exp; } // One usage is ambiguous. exp: exp '/' exp { $$ = $1 / $3; } // No error.
当发生歧义时,显式声明的名称可用于值和 地点。显式名称声明为符号后面的括号名称 在规则定义中的外观。例如:
exp[result]: exp[left] '/' exp[right] { $result = $left / $right; }
为了访问由 midrule 操作生成的语义值,一个 显式名称也可以通过在 Midrule 操作代码的右大括号:
exp[res]: exp[x] '+' {$left = $x;}[left] exp[right] { $res = $left + $right; }
在引用中,为了指定包含点和破折号的名称,显式
括号内的语法,必须使用:$[name]
@[name]
if-stmt: "if" '(' expr ')' "then" then.stmt ';' { $[if-stmt] = new_if_stmt ($expr, $[then.stmt]); }
经常发生命名引用后跟点、破折号或其他的情况
C 标点符号和运算符。默认情况下,Bison 将读取
'' 作为对符号值的引用,后跟
'',即对语义领域的访问
价值。为了迫使野牛在其
整体作为语义值的名称,括号内的语法
必须使用 ''。$name.suffix$name
.suffixsuffix
name.suffix$[name.suffix]
下一篇: 同一程序中的多个解析器, 上一篇: 命名引用, 上一篇: Bison 语法文件 [内容][索引]]
Bison 语法的 Bison 声明部分定义符号 用于表述语义值的语法和数据类型。 请参阅符号、终端和非终端。
必须声明所有令牌类型名称(但不包括单字符文字标记,如 and )。非终端符号必须是
如果需要指定要用于语义的数据类型,则已声明
值(请参阅多个值类型)。'+'
'*'
语法文件中的第一条规则还指定了开始符号,即 违约。如果您希望其他符号作为起始符号,则 必须显式声明它(请参阅语言和上下文无关语法)。
下一篇: 代币种类名称,上一篇: Bison Declarations [Contents][Index]
您可能需要 Bison 的最低版本来处理语法。如果
未满足要求,退出并显示错误(退出
状态 63)。bison
%require "version"
某些已弃用的行为被禁用,某些行为需要:version
“3.2”
(或更高)C++ 已弃用的文件和 否 生成时间更长。position.hhstack.hh
下一篇: 运算符优先级, 上一篇: 需要 Bison 版本, 上一篇: Bison 声明 [内容][索引]
声明令牌种类名称(终端符号)的基本方法如下:
%token name
Bison 会将其转换为解析器中的定义,以便
函数(如果它在此文件中)可以使用名称来
代表这种令牌类型的代码。yylex
name
或者,您可以使用 、 、 、
或者代替 ,如果要指定
关联性和优先级。请参阅运算符优先级。但是,对于
为了清楚起见,我们建议仅使用这些指令来声明关联性
和优先级,而不是添加字符串别名、语义类型等。%left
%right
%precedence
%nonassoc
%token
您可以通过附加 非负十进制或十六进制整数值立即出现在字段中 在令牌名称之后:
%token NUM 300 %token XNUM 0x12d // a GNU extension
然而,通常最好让 Bison 为所有人选择数字代码 令牌种类。Bison 将自动选择与 彼此之间或与普通字符。
如果堆栈类型是联合,则必须扩充 或其他令牌声明以包含数据类型
用尖括号分隔的备选方案(请参阅多个值类型)。%token
例如:
%union { /* define stack type */ double val; symrec *tptr; } %token <val> NUM /* define token NUM and its type */
您可以通过编写
声明末尾的文字字符串,声明
名称。例如:%token
%token ARROW "=>"
例如,C 语言的语法可能使用 等效的文字字符串标记:
%token <operator> OR "||" %token <operator> LE 134 "<=" %left OR "<="
将文本字符串和令牌种类名称等同起来后,就可以使用它们了
在进一步的声明或语法规则中可互换。该函数可以使用令牌名称或文本字符串来获取
令牌种类代码(请参阅 yylex
的调用约定)。yylex
字符串别名允许使用文本字符串提供更好的错误消息 而不是令牌名称,例如“”而不是“”。syntax error, unexpected ||, expecting number or (syntax error, unexpected OR, expecting NUM or LPAREN
字符串别名也可以标记为国际化(请参阅令牌国际化):
%token OR "||" LPAREN "(" RPAREN ")" '\n' _("end of line") <double> NUM _("number")
将用法语“”而不是“”产生。erreur de syntaxe, || inattendu, attendait nombre ou (erreur de syntaxe, || inattendu, attendait number ou (
使用 、 、 或 声明声明 标记并指定其优先级和关联性,
一下子。这些称为优先级声明。
有关运算符的一般信息,请参阅运算符优先级
优先。%left
%right
%nonassoc
%precedence
优先级声明的语法与以下语句几乎相同:%token
%left symbols…
或
%left <type> symbols…
事实上,这些声明中的任何一项都服务于 .
但除此之外,它们还指定了
所有 :%token
symbols
%left
xy%right
yz%nonassoc
x op y op
z%precedence
仅赋予 的优先级,并定义
完全没有关联性。使用它仅定义优先级,并保留任何
启用了由于关联性而导致的潜在冲突。symbols
为了向后兼容,两者之间存在令人困惑的差异
参数列表和优先级声明。只有 a 可以将文本字符串与标记类型名称相关联。一个
precedence 声明始终将文本字符串解释为对
一个单独的令牌。例如:%token
%token
%left OR "<=" // Does not declare an alias. %left OR 134 "<=" 135 // Declares 134 for OR and 135 for "<=".
用于指定多个值类型时,必须
声明每个非终端符号的值类型,其值为
使用。这是通过声明完成的,如下所示:%union
%type
%type <type> nonterminal…
下面是一个非终端符号的名称,并且是您想要的替代符号中给出的名称
(见《联盟宣言》)。您可以在
如果它们具有相同的值类型,则声明相同。使用空间
分隔符号名称。nonterminaltype%union
%type
虽然 POSIX Yacc 只允许非终端,但 Bison 接受
该指令也适用于终端符号。声明
专为非终端符号,使用更安全的:%type
%nterm
%nterm <type> nonterminal…
用于声明符号的各种指令的语法如下。
%token tag? ( id number? string? )+ ( tag ( id number? string? )+ )* %left tag? ( id number?)+ ( tag ( id number? )+ )* %type tag? ( id | char | string )+ ( tag ( id | char | string )+ )* %nterm tag? id+ ( tag id+ )*
其中表示类型标记,例如“”,表示 标识符,例如“”、十进制或十六进制 整数,例如“”或“”,字符文字 例如 '',以及字符串文本,例如 ‘’.后缀量词是 ''(零或一), ''(零或更多)和 ''(一个或多个)。tag<ival>idNUMnumber3000x12dchar'+'string"number"?*+
指令 ,并表现
喜欢。%precedence
%right
%nonassoc
%left
有时,解析器需要在解析之前执行一些初始化。
该指令允许此类任意代码。%initial-action
声明在每次调用解析之前必须调用 braced。可以使用(或)和 — 的初始值和位置
展望未来 — 以及 .codeyyparse
code$$
$<tag>$
@$
%parse-param
例如,如果您的位置使用文件名,则可以使用
%parse-param { char const *file_name }; %initial-action { @$.initialize (file_name); };
在错误恢复期间(请参阅错误恢复),符号已推送
来自文件其余部分的堆栈和令牌将被丢弃,直到
解析器站起来。如果解析器内存不足,或者如果它
返回 ,或 ,所有
必须丢弃堆栈上的符号。即使解析器成功,它
必须放弃开始符号。YYABORT
YYACCEPT
YYNOMEM
当丢弃的符号传达基于堆的信息时,此内存是 失去。虽然这种行为对于批处理分析器是可以容忍的,例如 在传统的编译器中,对于像 shell 或 可以无限期解析和执行的协议实现。
该指令定义在以下情况下调用的代码
符号被自动丢弃。%destructor
每当解析器丢弃其中一个 .内 , (或 )
指定与丢弃的符号关联的语义值,并指定其位置。其他解析器参数包括
也可用(参见解析器函数 yyparse
)。codesymbolscode$$
$<tag>$
@$
当一个符号列在 中时,它称为
每个符号 。
您还可以通过列出语义类型来定义每个类型
标记。
在这种情况下,解析器将在丢弃任何
具有该语义类型标记的语法符号,除非该符号有自己的
每个符号 。symbols%destructor
%destructor
%destructor
symbolscode%destructor
最后,您可以定义两种不同类型的默认值。
您可以将每个 和 放在
语法文件中只有一个声明。
每当
丢弃任何没有 per-symbol 和 per-type 的用户定义语法符号。
对于此类语法,解析器使用 for
已正式声明语义类型标记的符号(、、 和 算作此类声明,但不计入此类声明)。
对于此类语法,解析器使用 for
没有声明的语义类型标记的符号。%destructor
<*>
<>
symbols%destructor
code%destructor
code<*>
%token
%nterm
%type
$<tag>$
code<>
例如:
%union { char *string; } %token <string> STRING1 STRING2 %nterm <string> string1 string2 %union { char character; } %token <character> CHR %nterm <character> chr %token TAGLESS %destructor { } <character> %destructor { free ($$); } <*> %destructor { free ($$); printf ("%d", @$.first_line); } STRING1 string1 %destructor { printf ("Discarding tagless symbol.\n"); } <>
保证,当解析器丢弃任何具有
语义类型标记,它传递其语义值
默认设置为。
但是,当解析器丢弃 a 或 a 时,
它使用第三个,它释放了它和
将其行号打印为 ( 仅调用一次)。
最后,解析器只在丢弃任何符号时打印一条消息,
例如,没有语义类型标签。<character>
free
STRING1
string1
%destructor
stdout
free
TAGLESS
Bison 生成的解析器仅调用默认的 s
用户定义的符号,而不是 Bison 定义的符号。
例如,解析器不会为特殊的 Bison 定义的符号 、 或(参见 Bison Symbols)调用任何一种默认值。
这些都不能在语法中引用。
它也不会为令牌调用(参见 Bison 符号),无论您是否
在语法中引用它。
但是,如果满足以下条件,它可能会为结束令牌(令牌 0)调用其中之一
将其从 to 重新定义,例如:%destructor
%destructor
$accept
$undefined
$end
error
$end
END
%token END 0
最后,Bison 永远不会为未引用的
midrule 语义值(请参阅 Midrule 中的操作)。
也就是说,Bison 不认为中间线具有语义值,如果你
不要在中间线的操作中引用,或者(其中是中间线的右侧符号位置)在
该规则中的任何后续操作。但是,如果您确实引用了其中任何一个,则
Bison 生成的解析器将调用
它丢弃中间线符号。%destructor
$$
$n
n<>
%destructor
丢弃的符号如下:
parse
由于显式调用 、 或 错误恢复失败,解析器可以立即返回
或记忆力耗尽。YYABORT
YYACCEPT
YYNOMEM
显式触发语法的规则的右侧符号
错误过孔不会自动丢弃。通常
根据经验,只有当用户操作无法管理时,才会调用析构函数
记忆。YYERROR
启用运行时跟踪后(请参阅跟踪解析器), 解析器报告其操作,例如减少。当涉及符号时 在报告操作时,仅显示其类型,因为解析器不能 了解语义值的格式。
该指令定义当符号
报道。它的语法与(请参阅释放丢弃的符号)相同。%printer
%destructor
每当解析器显示 .其中 表示输出流(C 中的 a、C++ 中的 an 和 D),(或 )指定与
符号及其位置。其他解析器参数包括
也可用(参见解析器函数 yyparse
)。codesymbolscodeyyo
FILE*
std::ostream&
stdout
$$
$<tag>$
@$
它们被定义为 for (参见 Freeing Discarded Symbols.):它们可以是每个类型(例如,
'')、每个符号(例如,''、''、'')、
按默认键入(即 ''),或未按默认键类型(即
‘’).symbols%destructor
<ival>expNUM"float"<*><>
例如:
%union { char *string; } %token <string> STRING1 STRING2 %nterm <string> string1 string2 %union { char character; } %token <character> CHR %nterm <character> chr %token TAGLESS %printer { fprintf (yyo, "'%c'", $$); } <character> %printer { fprintf (yyo, "&%p", $$); } <*> %printer { fprintf (yyo, "\"%s\"", $$); } STRING1 string1 %printer { fprintf (yyo, "<>"); } <>
保证,当解析器打印任何具有语义类型的符号时
标记,它显示语义的地址
值。但是,当解析器显示 a 或 a 时,它会将其格式化为双引号中的字符串。它执行
在这种情况下只有第二个,所以它只打印一次。
最后,解析器为任何符号打印 '',例如
没有语义类型标记。有关完整示例,请参阅为 mfcalc
启用调试跟踪。<character>
STRING1
string1
%printer
<>TAGLESS
Bison 通常会在语法中出现任何冲突时发出警告
(参见 Shift/Reduce Conflicts),但大多数真正的语法
具有无害的转移/减少冲突,这些冲突在可预测的范围内得到解决
方式,并且很难消除。最好抑制
关于这些冲突的警告,除非冲突的数量
变化。您可以使用声明执行此操作。%expect
声明如下所示:
%expect n
这是一个十进制整数。宣言说应该有 是转移/减少冲突,没有减少/减少冲突。 如果 shift/reduce 冲突的数量不同,Bison 会报告错误 from ,或者如果存在任何 reduce/reduce 冲突。nnn
对于确定性解析器,reduce/reduce 冲突更多 严重,应完全消除。Bison 将始终报告 减少/减少这些分析器的冲突。使用 GLR 然而,解析器,这两种冲突都是例行公事;否则 无需使用 GLR 解析。因此,它是 还可以指定预期的减少/减少冲突数量 在 GLR 解析器中,使用声明:
%expect-rr n
您可能希望在您的
预期冲突的规范。为此,您还可以将修饰符附加到各个规则。
这些修饰符的解释与它们的用法不同
声明。当附加到规则时,它们指示状态数
其中规则涉及冲突。您需要咨询
用于确定要使用的适当数字的输出。
例如,对于以下语法片段,第一个规则出现在两个状态中,其中“”标记是
展望未来。确定这一点后,您可以使用修饰符记录此事实,如下所示:%expect
%expect-rr
-vempty_dims
[%expect
dims: empty_dims | '[' expr ']' dims ; empty_dims: %empty %expect 2 | empty_dims '[' ']' ;
中间规则操作会生成隐式规则,这些规则也会发生冲突
(请参阅由于 Midrule 操作导致的冲突)。附加
对隐式
中间规则操作的规则,放在操作之前。例如%expect
%expect-rr
%glr-parser %expect-rr 1 %% clause: "condition" %expect-rr 1 { value_mode(); } '(' exprs ')' | "condition" %expect-rr 1 { class_mode(); } '(' types ')' ;
在这里,适当的中间规则操作要等到之后才能确定 '' 令牌被移动。因此 这两个行动将相互冲突,我们应该期待一个 减少/减少每个冲突。(
通常,使用涉及以下步骤:%expect
%expect
-v%expect
n%expect-rr
%expect-rr
%expect
现在,如果您引入意外冲突,Bison 将报告错误, 但否则会保持沉默。
Next: 一个纯粹的(重入)解析器, Previous: 抑制冲突警告, Up: Bison Declarations [Contents][Index]
默认情况下,Bison 假定语法的起始符号是第一个
语法规范部分中指定的非终端。程序员
可以使用以下声明覆盖此限制:%start
%start symbol
Next: 推送解析器, Previous: The Start-Symbol, Up: Bison Declarations [Contents][Index]
重入计划是一个在以下过程中不会改变的计划 执行;换句话说,它完全由纯(只读)组成 法典。只要可以异步执行,重入就很重要; 例如,非重入程序可能无法安全地从信号调用 处理器。在具有多个控制线程的系统中,非可重入者 程序只能在联锁中调用。
通常,Bison 会生成一个不可重入的解析器。这是
适用于大多数用途,并允许与 Yacc 兼容。(
标准 Yacc 接口本质上是不可重入的,因为它们使用
静态分配的变量用于与 通信
包括 和 .)yylex
yylval
yylloc
或者,您可以生成一个纯的可重入解析器。野牛 声明 '' 表示您希望解析器是 折返。它看起来像这样:%define api.pure
%define api.pure full
结果是通信变量和在 中成为局部变量,并且
调用约定用于词法分析器函数。
有关详细信息,请参阅纯解析器的调用约定。变量在拉取模式下变为本地变量,但在推送模式下变为本地变量。(请参阅错误报告功能 yyerror
)。这
调用本身的约定是不变的。yylval
yylloc
yyparse
yylex
yynerrs
yyparse
yypstate
yyparse
解析器是否纯,与语法规则无关。 您可以从任何 有效的语法。
下一篇: Bison 声明摘要, Previous: 纯(重入)解析器, Up: Bison 声明 [内容][索引]
拉取解析器被调用一次,它就会控制,直到其所有输入 完全解析。另一方面,推送解析器称为 每次提供新令牌时。
当推送解析器是 客户端应用程序中的主事件循环。这通常是 当需要触发主事件循环时,对 GUI 的要求 在一定时间段内。
通常,Bison 会生成一个拉取解析器。 以下 Bison 声明说您希望解析器是推送 解析器(参见 %define 摘要):
%define api.push-pull push
在几乎所有情况下,您都希望确保推送解析器也 纯解析器(请参阅纯(重入)解析器)。唯一的 你应该创建一个不纯的推送解析器的时间是向后 与不纯的 Yacc 拉取模式接口兼容。除非你知道 你在做什么,你的声明应该如下所示:
%define api.pure full %define api.push-pull push
纯推送解析器之间存在一个显著的功能差异 和不纯的推送解析器。纯推送解析器具有 内存中同时存在许多相同类型的解析器的解析器实例。 不纯的推送解析器一次只能使用一个解析器。
当选择推送解析器时,Bison 将在
生成的解析器。 是生成的结构
解析器用于存储解析器的状态。 是
函数,以创建新的解析器实例。 将释放与相应解析器实例关联的资源。
最后,是每当
令牌可用于提供解析器。一个微不足道的例子
使用纯推送解析器如下所示:yypstate
yypstate_new
yypstate_delete
yypush_parse
int status; yypstate *ps = yypstate_new (); do { status = yypush_parse (ps, yylex (), NULL); } while (status == YYPUSH_MORE); yypstate_delete (ps);
如果用户决定使用不纯的推送解析器,那么关于
生成的解析器将更改。变量变为全局变量
变量,而不是函数中的局部变量。为
因此,函数的签名更改为
删除令牌作为参数。非重入推送解析器示例将
因此看起来像这样:yychar
yypush_parse
yypush_parse
extern int yychar; int status; yypstate *ps = yypstate_new (); do { yychar = yylex (); status = yypush_parse (ps); } while (status == YYPUSH_MORE); yypstate_delete (ps);
就是这样。请注意,下一个标记被放入全局变量中,供下次调用该函数使用。yychar
yypush_parse
Bison 还支持推送解析器接口和拉取解析器
接口。为了获得此功能,
您应该将 '' 声明替换为
'' 声明。这样做将创建所有
前面提到的符号以及两个额外的符号,以及 . 可以完全像往常一样使用
将被使用。但是,用户应注意,它是在
通过调用生成解析器。
这使得使用
'' 声明比正常函数慢。如果用户
调用函数,它将解析输入的其余部分
流。可以标记以选择子语法
然后是输入流的其余部分。如果你愿意
要在解析样式之间来回切换,您必须
编写自己的函数,知道何时停止查找
用于输入。使用该函数的示例将显示
喜欢这个:%define api.push-pull push%define api.push-pull bothyyparse
yypull_parse
yyparse
yypull_parse
yyparse
%define api.push-pull bothyyparse
yypull_parse
yypush_parse
yypull_parse
yypull_parse
yypull_parse
yypstate *ps = yypstate_new (); yypull_parse (ps); /* Will call the lexer */ yypstate_delete (ps);
添加 '' 声明对 生成的带有 '' 的解析器,就像它所做的那样 ‘’.%define api.pure%define api.push-pull both%define api.push-pull push
Next: %define Summary, Previous: A Push Parser, Up: Bison Declarations [Contents][Index]
以下是用于定义语法的声明的摘要:
要更改 的行为,请使用以下命令
指令:bison
将逐字插入到输出解析器源中 默认位置或位于 指定的位置。 请参见%code 摘要。codequalifier
定义一个变量来调整 Bison 的行为。请参阅 %define 摘要。
指定要用于所有 Bison 输出文件名的前缀。名称 被选择,就好像语法文件被命名为 一样。prefix.y
编写包含令牌种类名称定义的分析器头文件 在语法以及其他一些声明中定义。如果解析器 实现文件被命名,然后被命名为解析器头文件 被命名为 。name.cname.h
对于 C 解析器,解析器头文件声明 除非已定义为宏,或者您使用了标记而没有使用 。因此,如果你是
将(请参阅多个值类型)与需要
其他定义,或者如果您已定义宏或类型
定义(参见语义值的数据类型),您需要安排这些定义
传播到所有模块,例如,将它们放在先决条件中
解析器和任何其他模块都包含的标头
需要。YYSTYPE
YYSTYPE
<type>
%union
%union
YYSTYPE
YYSTYPE
除非解析器是纯解析器,否则解析器头文件将声明为外部变量。请参阅纯(重入)解析器。yylval
如果还使用了位置,则解析器头文件声明并使用类似于宏 和 的协议。请参阅追踪位置。YYLTYPE
yylloc
YYSTYPE
yylval
如果您希望将
的定义,因为通常需要能够引用
上述声明和令牌种类代码。请参阅标记的语义值。yylex
yylex
如果已声明 或 ,则输出
header 还包含他们的代码。
请参见%code 摘要。%code requires
%code provides
生成的标题受到 C 的保护,防止多次包含 预处理器保护: '',其中 和 是前缀(请参阅同一程序中的多个解析器)和 生成的文件名变为大写,每个系列都是非字母数字 字符转换为单个下划线。YY_PREFIX_FILE_INCLUDEDPREFIXFILE
例如,使用 '' 和 '',标头将按如下方式保护。%define api.prefix {calc}%header "lib/parse.h"
#ifndef YY_CALC_LIB_PARSE_H_INCLUDED # define YY_CALC_LIB_PARSE_H_INCLUDED ... #endif /* ! YY_CALC_LIB_PARSE_H_INCLUDED */
在 Bison 3.8 中引入。
与上面相同,但保存在文件中。header-file
指定生成的分析器的编程语言。现在 支持的语言包括 C、C++、D 和 Java。 是 不区分大小写。language
生成处理位置的代码(请参阅在操作中使用的特殊功能)。这 一旦语法使用特殊的“”,就会启用模式 标记,但如果你的语法不使用它,使用 '' 允许 以获得更准确的语法错误消息。@n%locations
被“”淘汰。请参阅同一程序中的多个解析器。有关 C++ 解析器,请参阅 '' 文档。%define api.prefix {prefix}%define api.namespace
重命名分析器中使用的外部符号,使其以“而不是”开头。在 C 中重命名的符号的精确列表
解析器是 、 、 、 、 、 和 (如果使用位置)。如果使用推送解析器,则 、 、 和 也将重命名。例如,如果使用
'',则名称变为 、 和
依此类推。prefixyyyyparse
yylex
yyerror
yynerrs
yylval
yychar
yydebug
yylloc
yypush_parse
yypull_parse
yypstate
yypstate_new
yypstate_delete
%name-prefix "c_"c_parse
c_lex
与定义相反,有些符号不会被重命名
例如,通过 、 、 、 。api.prefix
%name-prefix
YYDEBUG
YYTOKENTYPE
yytoken_kind_t
YYSTYPE
YYLTYPE
不要在分析器中生成任何预处理器命令
实现文件。通常,Bison 在解析器中编写这些命令
实现文件,以便 C 编译器和调试器将关联
源文件(语法文件)的错误和目标代码。这
指令使它们将错误与解析器实现相关联
文件,将其视为独立的源文件。#line
在 中生成解析器实现。file
“”的弃用版本(参见 %define Summary),Bison 对此更谨慎地警告 不合理使用。%define api.pure
需要 Bison 的 Vison版本或更高版本。请参阅需要 Bison 版本。version
指定要使用的骨架。
如果不包含 ,则为骨架的名称
文件。
如果是这样,则是一个绝对文件名或相对于
语法文件的目录。
这类似于大多数 shell 解析命令的方式。file/
filefile
此功能已过时,请在新项目中避免使用。
在分析器实现文件中生成令牌名称数组。这
数组的名称是 ; 是
其内部 Bison 令牌代码为 的令牌。前三个
元素对应于预定义的标记 、 和 ;在这些之后是
语法文件中定义的符号。yytname
yytname[i]
iyytname
"$end"
"error"
"$undefined"
表中的名称包括表示
野牛中的令牌。对于单字符文本和文本字符串,这
包括周围的引号字符和任何转义序列。为
例如,Bison 单字符文字对应于
三个字符的名称,在 C 中表示为 ;和野牛
两个字符的文字字符串对应于五个字符
name,在 C 中表示为 .'+'
"'+'"
"\\/"
"\"\\\\/\""
指定 时,Bison 还会生成宏定义
对于宏 、 和 ,和 :%token-table
YYNTOKENS
YYNNTS
YYNRULES
YYNSTATES
YYNTOKENS
终端符号的数量,即最高令牌代码加一。
YYNNTS
非终端符号的数量。
YYNRULES
语法规则的数量,
YYNSTATES
解析器状态的数量(请参阅解析器状态)。
下面是用于查找多字符令牌的代码,
假设令牌的字符存储在 中
并假设令牌不包含任何字符,例如“”
这需要逃避。yytname
token_buffer
"
for (int i = 0; i < YYNTOKENS; i++) if (yytname[i] && yytname[i][0] == '"' && ! strncmp (yytname[i] + 1, token_buffer, strlen (token_buffer)) && yytname[i][strlen (token_buffer) + 1] == '"' && yytname[i][strlen (token_buffer) + 2] == 0) break;
不鼓励使用此方法:字符串别名的主要目的是伪造 良好的错误消息,没有描述关键字的拼写。另外 在运行时查找令牌类型会产生(小但明显的)成本。
最后,与变量的 和 值不兼容。%token-table
custom
detailed
parse.error
%define
假装给出了选项 (参见 --yacc),即模仿 Yacc,包括其 命名约定。只有骨架才有意义。有关详细信息,请参阅调整解析器。--yaccyacc.c
当然,作为 Bison 的扩展,有点
自相矛盾......%yacc
野牛行为的许多特征可以通过以下方式控制
为要素分配单个值。由于历史原因,一些这样的
功能由专用指令赋值,例如
分配开始符号。但是,更新的此类功能是相关的
变量,由指令赋值:%start
%define
定义为 。variablevalue
值的类型取决于语法。大括号表示 目标语言(例如,命名空间、类型等)。关键字值(否 分隔符)表示有限选择(例如,特征的变体)。字符串 值表示剩余的情况(例如,文件名)。
如果 a 由多个定义,则为错误
次,但请参见 -D name[=value]。variable%define
本节的其余部分总结了接受的变量和值。%define
有些采用布尔值。在这种情况下,野牛会抱怨 如果变量定义不满足以下四个条件之一 条件:variable
value
是true
value
被省略(或被指定)。
这相当于 。""
true
value
是。false
什么是被接受的,以及它们的含义和默认值 值,取决于所选的目标语言和/或分析器骨架 (见野牛宣言摘要,见野牛宣言摘要)。 未接受的 s 会产生错误。下面介绍了一些被接受的 s。variablevariablevariable
==
!=
const std::string
filename_type
std::string
api.filename.type
const
std::string
从历史上看,当使用 option 或时,会生成一个标头并将其的精确副本粘贴到
生成的解析器实现文件。从 Bison 3.6 开始,它是 d 作为 '',而不是重复的,除非是 '',见下文。-d--headerbison
#include
"basename.h"filey.tab
该变量允许控制如何生成
解析器生成的标头。例如:api.header.include
#include
%define api.header.include {"parse.h"}
或
%define api.header.include {<parser/parse.h>}
使用不会更改生成的名称
标头,只是它的包含方式。api.header.include
要解决 Automake(与 一起运行)的限制,当输出文件为 时,未预定义 。将其定义为
避免重复。ylwrap
bison
--yaccapi.header.include
y.tab.c
#include
api.header.include
"parse.h""calc/parse.h"none
¶none
如果启用了位置,则在头文件中生成 和 类的定义,否则为
分析器实现。position
location
%header
生成 和 类的定义
在。此文件名可以是相对的(与解析器文件所在的位置
output) 或绝对值。position
location
file
位置
) 中生成。api.location.type
location
location.hhposition
location
location
#include
%define api.namespace {foo::bar}
Bison 在参考文献中逐字使用,例如:foo::bar
foo::bar::parser::value_type
但是,要打开命名空间,Bison 会删除任何前导,然后
对任何剩余的匹配项进行拆分:::
namespace foo { namespace bar { class position; class location; } }
"::"
"foo"
"::foo::bar"
yy
%name-prefix "prefix"parser
YYParser
api.prefixParser
parser_class_name
YY
yy
true
false
full
可以省略该值:这等效于按原样指定
布尔值的情况。true
使用时,解析器将重入。这
更改签名(请参阅纯解析器的调用约定),以及激活位置跟踪时的签名,如下所示
下面。%define api.pure full
yylex
yyerror
该值与该值非常相似,唯一的
不同之处在于 在 Yacc 解析器上没有签名,出于历史原因。true
full
yyerror
%parse-param
也就是说,如果传递了 '',则 的原型是:%locations %define api.pureyyerror
void yyerror (char const *msg); // Yacc parsers. void yyerror (YYLTYPE *locp, char const *msg); // GLR parsers.
但是,如果 '' 是 used,则两个解析器具有相同的签名:%locations %define api.pure %parse-param {int *nastiness}
void yyerror (YYLTYPE *llocp, int *nastiness, char const *msg);
(请参阅错误报告功能 yyerror
)
false
full
pull
push
both
pull
%define api.symbol.prefix {S_} %token FILE for ERROR %% start: FILE for ERROR;
在 C 语言中生成以下定义:
/* Symbol kind. */ enum yysymbol_kind_t { S_YYEMPTY = -2, /* No symbol. */ S_YYEOF = 0, /* $end */ S_YYERROR = 1, /* error */ S_YYUNDEF = 2, /* $undefined */ S_FILE = 3, /* FILE */ S_for = 4, /* for */ S_ERROR = 5, /* ERROR */ S_YYACCEPT = 6, /* $accept */ S_start = 7 /* start */ };
空前缀(通常)无效:
YYERROR
SymbolKind
YYSYMBOL_
S_
%define api.token.prefix {TOK_} %token FILE for ERROR %% start: FILE for ERROR;
在生成的源文件中生成符号 、 和 的定义。特别是扫描仪
必须使用这些带前缀的标记名称,而语法本身可能仍使用
短名称(如上面给出的示例规则所示)。生成的
信息文件 (, , ) 不是
由此前缀修改。TOK_FILE
TOK_for
TOK_ERROR
*.output*.xml*.gv
Bison 还为语义值并集的生成成员名称添加前缀。 有关详细信息,请参阅生成语义值类型 详。
有关完整示例,请参阅 Calc++ Parser 和 Calc++ Scanner。
设置后,令牌种类的代码被强制为
与符号种类相吻合。这样可以节省每个要映射的令牌的表查找
它们从令牌种类到符号种类,也节省了一代
的映射表。增益通常适中,但在极端情况下
(非常简单的用户操作),可以观察到 10% 的改进。api.token.raw
设置后,语法不能使用字符文字
(例如“”)。api.token.raw
'a'
true
false
exp: "number" { $$ = make_number ($1); } | exp "+" exp { $$ = make_binary (add, $1, $3); } | "(" exp ")" { $$ = $2; }
实际上编译得好像你写过:
exp: "number" { $$ = make_number (std::move ($1)); } | exp "+" exp { $$ = make_binary (add, std::move ($1), std::move ($3)); } | "(" exp ")" { $$ = std::move ($2); }
在启用自动移动的情况下多次使用某个值通常是错误的。 例如,而不是:
exp: "twice" exp { $$ = make_binary (add, $2, $2); }
写:
exp: "twice" exp { auto v = $2; $$ = make_binary (add, v, v); }
在其中一个上使用是很诱人的,但是
C++ 中的参数计算顺序未指定。std::move
v
false
这种语法根本没有语义价值。这没有得到适当的支持 还。
由于该指令,该类型是定义的。你没有
在这种情况下定义,使用就足够了。
见《国际电联宣言》。
例如:%union
api.value.type
%union
%define api.value.type union-directive %union { int ival; char *sval; } %token <ival> INT "integer" %token <sval> STR "string"
符号使用类型名称定义,Bison 将从中生成 .例如:union
%define api.value.type union %token <int> INT "integer" %token <char *> STR "string"
大多数 C++ 对象不能存储在 ,使用 ''
相反。union
variant
这类似于 ,但使用特殊的存储技术来
允许使用任何类型的 C++ 对象。例如:union
%define api.value.type variant %token <int> INT "integer" %token <std::string> STR "string"
请参阅 C++ 变体。
将此用作语义值。type
%code requires { struct my_value { enum { is_int, is_str } kind; union { int ival; char *sval; } u; }; } %define api.value.type {struct my_value} %token <u.ival> INT "integer" %token <u.sval> STR "string"
union-directive
如果使用,否则......%union
int
如果使用 type 标记(即 '' 或
''),否则......%token <type>…%nterm <type>…stype
union
typedef
id
%union
idYYSTYPE
most
consistent
accepting
accepting
如果是 .lr.type
canonical-lr
most
否则。lr.default-reductions
lr.default-reduction
false
lr.keep_unreachable_states
lr.keep-unreachable-states
lr.keep-unreachable-state
已过时api.namespace
在 C++ 中,使用变体时(请参阅 C++ 变体),符号必须是 正确建造和销毁。此选项检查这些约束 使用运行时类型信息 (RTTI)。因此,生成的代码不能 在禁用 RTTI 的情况下进行编译(通过编译器选项,例如 )。-fno-rtti
false
simple
传递给的错误消息只是 .yyerror
"syntax error"
detailed
错误消息报告意外令牌,也可能是预期的令牌。
但是,当未启用 LAC 时,此报告通常不正确
(见 LAC)。支持令牌名称国际化。verbose
与 相似(但不如)。D 分析器不支持此值。detailed
错误消息报告意外令牌,也可能是预期的令牌。 但是,当未启用 LAC 时,此报告通常不正确 (见 LAC)。
不支持令牌国际化。在 令牌别名不可移植。
custom
用户负责通过定义函数来生成语法错误消息。请参阅语法错误报告功能yyreport_syntax_error
。yyreport_syntax_error
simple
simple
verbose
custom
detailed
none
full
none
在 C/C++ 中,定义宏(或使用
''),请参阅同一程序中的多个解析器)改为
1(如果尚未定义),以便调试工具是
编译。YYDEBUG
prefixDEBUG
%define api.prefix {prefix}
false
已过时api.parser.class
上一篇: %define 摘要, 上一篇: 野牛声明 [内容][索引]
该指令将代码逐字插入到输出中
解析器源位于预定义位置集中的任何一个位置。因此,它服务于
作为传统 Yacc 的灵活且用户友好的替代品
序幕。本节总结了
各种目标语言的功能
由野牛支持。详细讨论如何使用代替 C/C++ 以及为什么使用
这样做是有利的,请参阅序幕替代方案。%code
%{code%}
%code
%code
%{code%}
这是指令的无条件形式。它
在与语言相关的默认位置逐字插入
在解析器实现中。%code
code
对于 C/C++,默认位置是分析器实现文件
在分析器头文件的常规内容之后。因此,
在大多数情况下,不合格的表格会被替换。%{code%}
对于 D 和 Java,默认位置位于解析器类内。
这是指令的限定形式。 确定 的目的,因此
Bison 应插入它的位置。也就是说,如果需要
指定不属于
非合格表单选择的默认位置,使用
这种形式。%code
qualifiercodecode%code
对于任何特定的限定词或不合格的形式,如果有
指令的多次出现,Bison 连接
按其在语法中出现的顺序排列的指定代码
文件。%code
并非所有目标语言都接受所有限定词。未接受 限定符产生错误。一些公认的限定词是:
requires
¶YYSTYPE
YYLTYPE
%union
#define
YYSTYPE
YYLTYPE
%define
api.value.type
api.location.type
YYSTYPE
YYLTYPE
provides
¶YYSTYPE
YYLTYPE
top
¶%code
%code requires
%code top
%code top { #define _GNU_SOURCE #include <stdio.h> }
imports
¶虽然我们说插入位置与语言有关,但它们确实如此 从技术上讲,依赖于骨架。非标准骨架的编写者 但是,应根据行为选择其位置 标准野牛骨架。
上一篇: Bison Declarations, Up: Bison Grammar Files [Contents][Index]
大多数使用 Bison 的程序只解析一种语言,因此包含
只有一个 Bison 解析器。但是,如果您想解析多种语言,该怎么办
使用相同的程序?然后你需要避免名称冲突
函数和变量的不同定义,例如 、 。要使用来自同一编译单元的不同解析器,您需要
还需要避免类型和宏上的冲突(例如,)
导出到生成的标头中。yyparse
yylval
YYSTYPE
执行此操作的简单方法是定义变量。使用不同的 s,可以保证
标头在包含在一起时不会发生冲突,并且编译的对象
也可以链接在一起。指定 ''(或传递选项,请参阅调用 Bison)重命名接口函数和
Bison 解析器的变量开始,而不是
'',以及所有以(即大写)开头的宏,而不是 ''。%define
api.prefix
api.prefix
%define api.prefix
{prefix}-Dapi.prefix={prefix}prefixyyPREFIXprefixYY
重命名的符号包括 、 、 、 、 和 。如果使用推送解析器,则 、 、 和 也将重命名。重命名的宏包括 、 和 ,处理
具体来说,更多关于这个的信息见下文。yyparse
yylex
yyerror
yynerrs
yylval
yylloc
yychar
yydebug
yypush_parse
yypull_parse
yypstate
yypstate_new
yypstate_delete
YYSTYPE
YYLTYPE
YYDEBUG
例如,如果使用 '',则名称将变为 、 、 ...、 、 等
上。%define api.prefix {c}cparse
clex
CSTYPE
CLTYPE
Flex 用户必须更新生成函数的签名。由于 Flex 扫描仪通常包含生成的标头
解析器(获取令牌的定义等),最方便
方法是在以下部分中插入声明:yylex
yylex
provides
%define api.prefix {c} // Emitted in the header file, after the definition of YYSTYPE. %code provides { // Tell Flex the expected prototype of yylex. #define YY_DECL \ int clex (CSTYPE *yylval, CLTYPE *yylloc) // Declare the scanner. YY_DECL; }
该变量以两种不同的方式工作。
在实现文件中,它的工作原理是将宏定义添加到
解析器实现文件的开头,定义为 ,依此类推:%define
api.prefix
yyparse
prefixparse
#define YYSTYPE CTYPE #define yyparse cparse #define yylval clval ... YYSTYPE yylval; int yyparse (void);
这有效地将整个解析器中的一个名称替换为另一个名称
实现文件,因此“原始”名称 (, , ...) 也可以在解析器实现文件中使用。yylex
YYSTYPE
但是,在解析器头文件中,符号被定义为重命名,用于 实例:
extern CSTYPE clval; int cparse (void);
该宏通常用于启用跟踪支持
解析 器。为了遵守此传统,使用时,(未重命名)用作默认值:YYDEBUG
api.prefix
YYDEBUG
/* Debug traces. */ #ifndef CDEBUG # if defined YYDEBUG # if YYDEBUG # define CDEBUG 1 # else # define CDEBUG 0 # endif # else # define CDEBUG 0 # endif #endif #if CDEBUG extern int cdebug; #endif
在 Bison 2.6 之前,类似的功能由
过时的指令(参见野牛符号)和
选项(请参阅输出文件)。api.prefix
%name-prefix
--name-prefix
下一篇: Bison 解析器算法, 上一篇: Bison 语法文件, 上一篇: Bison [内容][索引]
Bison 解析器实际上是一个名为 的 C 函数。在这里,我们
描述 和 的接口约定
它需要使用的函数。yyparse
yyparse
请记住,解析器使用许多以 '' 和 '' 用于内部目的。如果您使用这样的 动作或结语中的标识符(本手册中的标识符除外) 在语法文件中,您可能会遇到麻烦。yyYY
yyparse
调用该函数以进行分析。这
函数读取令牌,执行操作,并最终在
遇到输入结束或不可恢复的语法错误。您还可以
编写一个指示立即返回的操作
无需进一步阅读。yyparse
yyparse
void
) ¶如果解析成功,则返回的值为 0(返回
是由于输入结束)。yyparse
如果由于无效输入(即输入)而解析失败,则值为 1
包含语法错误或导致
调用。YYABORT
如果由于内存耗尽而分析失败,则该值为 2。
在操作中,您可以使用以下方法立即返回
这些宏:yyparse
立即返回值为 0(报告成功)。
立即返回值为 1(报告失败)。
立即返回值 2(报告内存耗尽)。
如果使用可重入解析器,则可以选择传递其他
以可重入的方式向其提供参数信息。为此,请使用
声明:%parse-param
声明一个或多个是附加参数。
声明时使用
功能或原型。中的最后一个标识符必须是参数名称。argument-declarationyyparse
argument-declarationargument-declaration
下面是一个示例。在解析器中写下以下内容:
%parse-param {int *nastiness} {int *randomness}
然后像这样调用解析器:
{
int nastiness, randomness;
… /* Store proper data in nastiness
and randomness
. */
value = yyparse (&nastiness, &randomness);
…
}
在语法操作中,使用如下表达式来引用数据:
exp: … { …; *randomness += 1; … }
使用以下方法:
%parse-param {int *randomness}
结果如下:
void yyerror (int *randomness, const char *msg); int yyparse (int *randomness);
或者,如果两者兼而有之(或只是)
并使用:%define api.pure full
%define api.pure
%locations
void yyerror (YYLTYPE *llocp, int *randomness, const char *msg); int yyparse (int *randomness);
下一篇: 词汇分析器函数 yylex
, 上一篇: 解析器函数 yyparse
, 上一篇: 解析器 C 语言接口 [内容][索引]]
调用该函数以创建新的分析器实例。
如果 ''
或使用“”声明。请参阅推送解析器。yypstate_new
%define api.push-pull push%define api.push-pull both
void
) ¶如果有可用内存,则返回有效的解析器实例,否则返回 0。 在不纯模式下,如果解析器实例当前为 分配。
调用该函数以删除分析器实例。
如果 '' 或
'' 声明。
请参阅推送解析器。yypstate_delete
%define api.push-pull push%define api.push-pull both
yypstate *
yyps) ¶回收与解析器实例关联的内存。打完这个电话后,你 不应再尝试使用解析器实例。
调用函数来分析单个令牌。这
如果 '' 或
'' 声明。请参阅推送解析器。yypush_parse
%define api.push-pull push%define api.push-pull both
yypstate *
yyps) ¶返回的值与 for 相同,但存在以下例外:如果更多输入是
需要完成语法分析。yypush_parse
yyparse
YYPUSH_MORE
返回后,可以咨询实例。为
实例检查是否有(可能已恢复)
语法错误。yypush_parse
yynerrs
After 返回 以外的状态 ,
解析器实例可以重用于新的解析。yypush_parse
YYPUSH_MORE
yyps
即使在发生错误后,解析器状态也是可重用的,这一事实简化了
再使用。例如,解析每一行输入的计算器应用程序
因为表达式可以继续重用相同的内容,即使输入
无效。yyps
调用该函数以分析输入的其余部分
流。如果 ''
使用声明。请参阅推送解析器。yypull_parse
%define api.push-pull both
yypstate *
yyps) ¶返回的值与 返回的值相同。yypull_parse
yyparse
解析器实例可以重用于新的解析。yyps
const yypstate *
yyps, yysymbol_kind_t
argv[]
, int
argc) ¶填充预期的标记,其中从不包含 、 或 。argvYYSYMBOL_YYEMPTY
YYSYMBOL_YYerror
YYSYMBOL_YYUNDEF
永远不要把更多的元素放进去,在成功上
返回存储在 中的令牌数。如果还有更多
预期的令牌比 ,填满并返回
0. 如果没有预期的令牌,则也返回 0,但设置为 。argcargvargvargcargvargcargv[0]
YYSYMBOL_YYEMPTY
启用 LAC 后,可能会在错误时返回负数,
例如内存耗尽。YYENOMEM
如果为 null,则返回存储所有可能文件所需的大小
值,它总是小于 。argvYYNTOKENS
yylex
词法分析器函数 ,可识别来自
输入流,并将其返回到解析器。野牛不创造
此功能自动;你必须写它,这样才能
打电话给它。该函数有时称为词法扫描程序。yylex
yyparse
在简单的程序中,通常在Bison的末尾定义
语法文件。如果在单独的源文件中定义,则
需要安排令牌种类的定义在那里可用。待办事项
这样,在运行 Bison 时使用该选项,以便它将写入
这些定义将添加到单独的分析器头文件中,您可以将其包含在其他源文件中
需要它。请参阅调用 Bison。yylex
yylex
-dname.tab.h
下一篇: 特殊代币,向上:词汇分析器函数 yylex
[内容][索引]
yylex
返回的值必须是
它刚刚找到的那种代币;零值或负值表示
输入结束。yylex
当标记类型在语法规则中由名称引用时,该名称
在解析器实现中,文件成为枚举的枚举器,其定义是枚举的正确数字代码
令牌种类。因此,应使用名称来指示该类型。
请参阅符号、终端和非终端。yytoken_kind_t
yylex
当字符文字在语法规则中引用标记时,
该字符的数字代码也是令牌类型的代码。所以可以简单地返回该字符代码,可能转换为以避免符号扩展。空字符不得
以这种方式使用,因为它的代码为零,这意味着输入结束。yylex
unsigned char
下面是一个示例,显示了这些内容:
int yylex (void) { … if (c == EOF) /* Detect end-of-input. */ return YYEOF; … else if (c == '+' || c == '-') return c; /* Assume token kind for '+' is '+'. */ … else return INT; /* Return the kind of the token. */ … }
此接口经过精心设计,可以在不更改 定义的情况下使用实用程序的输出。lex
yylex
Next: 通过字符串文字查找标记, Previous: yylex
的调用约定, 上一篇: 词法分析器函数 yylex
[Contents][索引]
除了用户定义的令牌外,Bison 还会生成一些特殊令牌
可能会回来。yylex
令牌表示文件的结尾,并向解析器发出信号
之后什么都没有了。请参阅 yylex
的调用约定,了解
例。YYEOF
返回告诉解析器发现了一些词法错误。
它会发出一条关于“无效令牌”的错误消息,然后输入
error-recovery(请参阅错误恢复)。返回未知令牌种类
导致完全相同的行为。YYUNDEF
返回要求分析器在不发出错误消息的情况下输入错误恢复。这样,词法分析器可以
生成有关无效输入的准确错误消息(某些
解析器不能做),但受益于
解析 器。YYerror
int yylex (void) { … switch (c) { … case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': … return TOK_NUM; … case EOF: return YYEOF; default: yyerror ("syntax error: invalid character: %c", c); return YYerror; } }
下一篇: 标记的语义值, 上一篇: 特殊标记, 上一篇: 词汇分析器函数 yylex
[Contents][Index]
如果语法使用文字字符串标记,则有两种方法可以确定它们的标记类型代码:yylex
yylex
yylex
这是首选方法。
yylex
可以在表中搜索多字符令牌。不建议使用此方法:字符串别名的主要用途是
伪造良好的错误消息,而不是描述关键字的拼写。在
此外,在运行时查找令牌类型会产生 (小但
显着)成本。yytname
仅当使用声明时,才会生成该表。参见野牛宣言摘要。yytname
%token-table
下一篇: 标记的文本位置, 上一篇: 通过字符串文字查找标记, 上一篇: 词汇分析器函数 yylex
[Contents][索引]
在普通(非重入)解析器中,令牌的语义值必须
存储到全局变量中。当您仅使用
语义值的一种数据类型具有该类型。因此,如果
类型为(默认值),您可以将其写成:yylval
yylval
int
yylex
… yylval = value; /* Put value onto Bison stack. */ return INT; /* Return the kind of the token. */ …
当您使用多种数据类型时,的类型是联合制作的
从宣言(见《联盟宣言》)。所以当你存储
令牌的值,您必须使用适当的联合成员。如果声明如下所示:yylval
%union
%union
%union { int intval; double val; symrec *tptr; }
那么代码可能如下所示:yylex
… yylval.intval = value; /* Put value onto Bison stack. */ return INT; /* Return the kind of the token. */ …
下一篇: 纯解析器调用约定, 上一篇: 标记的语义值, 上一篇: 词法分析器函数 yylex
[Contents][索引]
如果您使用的是“”功能(请参阅跟踪位置)
在跟踪标记和分组的文本位置的操作中,
那么,您必须在 中提供此信息。该函数期望找到刚刚解析的标记的文本位置
在全局变量 中。所以必须存储适当的
该变量中的数据。@nyylex
yyparse
yylloc
yylex
默认情况下,的值是一个结构,您只需要
初始化操作将使用的成员。这
四个成员分别称为 、 和 。请注意,使用
功能使解析器明显变慢。yylloc
first_line
first_column
last_line
last_column
的数据类型名称为 。yylloc
YYLTYPE
上一篇: 标记的文本位置, 上一篇: 词汇分析器函数 yylex
[Contents][Index]
当您使用 Bison 声明请求
纯的、可重入的解析器,全局通信变量,不能使用。(请参阅纯(重入)解析器。在这样的解析器中,两者
全局变量被作为参数传递给 的指针替换。您必须声明它们,如下所示,并传递信息
通过这些指针存储它来返回。%define api.pure full
yylval
yylloc
yylex
int yylex (YYSTYPE *lvalp, YYLTYPE *llocp) { … *lvalp = value; /* Put value onto Bison stack. */ return INT; /* Return the kind of the token. */ … }
如果语法文件不使用“”结构来引用
文本位置,则不会定义类型。在
在这种情况下,省略第二个参数; 将被调用
只有一个参数。@YYLTYPE
yylex
如果您希望将其他参数传递给 ,请使用 just like (请参阅解析器函数 yyparse
)。要将其他参数传递给 和 ,请使用 。yylex
%lex-param
%parse-param
yylex
yyparse
%param
指定 that are additional argument
声明。您可以通过一个或多个此类声明,即
等同于重复。argument-declarationyylex
%lex-param
指定附加 / 参数声明。这相当于
‘’.您可以通过一项或多项
声明,这相当于重复.argument-declarationyylex
yyparse
%lex-param {argument-declaration} … %parse-param
{argument-declaration} …%param
例如:
%lex-param {scanner_mode *mode} %parse-param {parser_mode *mode} %param {environment_type *env}
生成以下签名:
int yylex (scanner_mode *mode, environment_type *env); int yyparse (parser_mode *mode, environment_type *env);
如果添加了 '':%define api.pure full
int yylex (YYSTYPE *lvalp, scanner_mode *mode, environment_type *env); int yyparse (parser_mode *mode, environment_type *env);
最后,如果 '' 和 是
使用:%define api.pure full%locations
int yylex (YYSTYPE *lvalp, YYLTYPE *llocp, scanner_mode *mode, environment_type *env); int yyparse (parser_mode *mode, environment_type *env);
Next: Actions 中使用的特殊功能, Previous: 词汇分析器函数 yylex
, Up: 解析器 C 语言接口 [内容][索引]
在执行过程中,解析器可能会有错误消息要传递给用户, 例如语法错误或内存耗尽。如何传递此消息 必须由开发人员指定给用户。
下一篇: 语法错误上报功能 yyreport_syntax_error
,上一篇:上报错误【内容】【索引】]
yyerror
Bison 解析器检测到语法错误(或解析错误)
每当它读取不能满足任何语法规则的标记时。一
语法中的 action 也可以显式声明错误,使用
宏(请参阅在操作中使用的特殊功能)。YYERROR
Bison 解析器希望通过调用错误来报告错误
必须提供名为 的报告函数。是的
每当发现语法错误时调用,并且它
接收一个参数。对于语法错误,字符串通常为 .yyerror
yyparse
"syntax error"
如果在
Bison 声明部分(参见 Bison 声明部分),然后 Bison 提供
一个更详细和具体的错误消息字符串,而不仅仅是普通的 .但是,该消息有时包含
如果未启用 LAC,则信息不正确(请参阅 LAC)。%define parse.error detailedcustom"syntax error"
解析器可以检测到另一种错误:内存耗尽。这
当输入包含非常深的结构时,可能会发生这种情况
嵌 套。你不太可能遇到这种情况,因为野牛
解析器通常会自动将其堆栈扩展到一个非常大的限制。但
如果内存耗尽,则通常调用
fashion,但参数字符串是 。yyparse
yyerror
"memory exhausted"
在某些情况下,诊断是
之前自动从英语翻译成其他语言
它们被传递给 .请参阅 Parser 国际化。"syntax error"
yyerror
以下定义在简单程序中就足够了:
void yyerror (char const *s) {
fprintf (stderr, "%s\n", s); }
返回后,后者将尝试
错误恢复(如果您编写了合适的错误恢复语法规则)
(请参阅错误恢复)。如果无法恢复,将
立即返回 1。yyerror
yyparse
yyparse
显然,在位置跟踪纯解析器中,应该有
对当前位置的访问。有了 ,这是
GLR 解析器确实如此,但 Yacc 解析器并非如此,因为
历史原因,这就是原因
首选。yyerror
%define api.pure
%define api.pure full
%define api.pure
使用时,具有
以下签名:%locations %define api.pure full
yyerror
void yyerror (YYLTYPE *locp, char const *msg);
原型只是 Bison 生成代码的指示
使用。Bison 生成的代码总是忽略返回的
值,因此可以返回任何类型,包括 .
此外,可以是可变函数;这就是为什么
消息始终最后传递。yyerror
yyerror
void
yyerror
传统上返回一个 总是
被忽略了,但这纯粹是出于历史原因,而且是
最好,因为它更准确地描述了 的返回类型。yyerror
int
void
yyerror
该变量包含语法错误的数量
到目前为止报道。通常,此变量是全局变量;但如果你
请求纯解析器(请参阅纯(重入)解析器)
那么它是一个局部变量,只有操作才能访问。yynerrs
上一篇: 错误上报功能 yyerror
, 上一篇: 上报错误 [内容][索引]
yyreport_syntax_error
如果调用 ''(请参阅 Bison 声明部分),则解析器不再将语法错误消息传递给 ,而是通过调用函数将该任务委托给用户。%define parse.error customyyerror
yyreport_syntax_error
以下函数和类型是 “”: 它们在
实现文件 () 并且只能从那里获得。他们
旨在从语法的结语中使用。static
*.c
const yypcontext_t *
ctx) ¶向用户报告语法错误。成功时返回 0,开
记忆力耗尽。是否使用取决于用户。YYENOMEM
yyerror
使用以下类型和函数生成错误消息。
捕获语法错误情况的不透明类型。
所有语法符号、标记和非终端的枚举。其 枚举器是从符号名称伪造的:
enum yysymbol_kind_t { YYSYMBOL_YYEMPTY = -2, /* No symbol. */ YYSYMBOL_YYEOF = 0, /* "end of file" */ YYSYMBOL_YYerror = 1, /* error */ YYSYMBOL_YYUNDEF = 2, /* "invalid token" */ YYSYMBOL_PLUS = 3, /* "+" */ YYSYMBOL_MINUS = 4, /* "-" */ [...] YYSYMBOL_VAR = 14, /* "variable" */ YYSYMBOL_NEG = 15, /* NEG */ YYSYMBOL_YYACCEPT = 16, /* $accept */ YYSYMBOL_exp = 17, /* exp */ YYSYMBOL_input = 18 /* input */ }; typedef enum yysymbol_kind_t yysymbol_kind_t;
const yypcontext_t *
ctx) ¶“意外”令牌:导致
语法错误。如果没有前瞻,则返回。YYSYMBOL_YYEMPTY
const yypcontext_t *
ctx) ¶语法错误的位置(意外标记的位置)。
const yypcontext_t *
ctx, yysymbol_kind_t
argv[]
, int
argc) ¶填充预期的标记,其中从不包含 、 或 。argvYYSYMBOL_YYEMPTY
YYSYMBOL_YYerror
YYSYMBOL_YYUNDEF
永远不要把更多的元素放进去,在成功上
返回存储在 中的令牌数。如果还有更多
预期的令牌比 ,填满并返回
0. 如果没有预期的令牌,则也返回 0,但设置为 。argcargvargvargcargvargcargv[0]
YYSYMBOL_YYEMPTY
启用 LAC 后,可能会在错误时返回负数,
例如内存耗尽。YYENOMEM
如果为 null,则返回存储所有可能文件所需的大小
值,它总是小于 。argvYYNTOKENS
symbol_kind_t
符号)¶其种类为 的符号的名称,可能已翻译。symbol
自定义语法错误函数如下所示。此实现是 不适合国际化,请参阅示例以获取更好的替代方案。c/bistromathic
static int yyreport_syntax_error (const yypcontext_t *ctx) { int res = 0; YYLOCATION_PRINT (stderr, *yypcontext_location (ctx)); fprintf (stderr, ": syntax error"); // Report the tokens expected at this point. { enum { TOKENMAX = 5 }; yysymbol_kind_t expected[TOKENMAX]; int n = yypcontext_expected_tokens (ctx, expected, TOKENMAX); if (n < 0) // Forward errors to yyparse. res = n; else for (int i = 0; i < n; ++i) fprintf (stderr, "%s %s", i == 0 ? ": expected" : " or", yysymbol_name (expected[i])); } // Report the unexpected token. { yysymbol_kind_t lookahead = yypcontext_token (ctx); if (lookahead != YYSYMBOL_YYEMPTY) fprintf (stderr, " before %s", yysymbol_name (lookahead)); } fprintf (stderr, "\n"); return res; }
您仍然必须提供一个函数,例如用于
报告内存耗尽。yyerror
下面是一个 Bison 结构、变量和宏的表格,它们在 行动。
;
¶立即从 返回,指示失败。
请参阅 Parser 函数 yyparse
。yyparse
;
¶立即返回 ,表示成功。
请参阅 Parser 函数 yyparse
。yyparse
);
¶取消移动令牌。此宏仅允许用于减少 单个值,并且仅当没有 Lookahead 令牌时。 在 GLR 解析器中也不允许这样做。 它安装一个具有令牌种类和 语义价值 ;然后它丢弃了 将因此规则而减少。tokenvalue
如果宏在无效时使用,例如当有 已经是 Lookahead 令牌,然后它报告语法错误 消息 '' 并执行普通错误 恢复。cannot back up
无论哪种情况,都不会执行操作的其余部分。
没有 lookahead 令牌时存储的值。yychar
当前瞻是输入结束时存储的值
流。yychar
;
¶立即导致语法错误。此语句引发错误
恢复,就好像解析器本身检测到错误一样;但是,它
不调用 ,也不打印任何消息。如果你
想要打印错误消息,请在之前显式调用
'' 语句。请参阅错误恢复。yyerror
yyerror
YYERROR;
;
¶立即从 返回,表示内存耗尽。
请参阅 Parser 函数 yyparse
。yyparse
包含 lookahead 令牌的变量,或者当
Lookahead 是输入流的结束,或者当没有 lookahead 时
已执行,因此下一个令牌尚不清楚。
不要在延迟语义操作中进行修改(请参阅 GLR 语义操作)。
请参阅 Lookahead 令牌。YYEOF
YYEMPTY
yychar
未设置包含 lookahead 令牌位置的变量
到 或 .
不要在延迟语义操作中进行修改(请参阅 GLR 语义操作)。
请参阅操作和位置。yychar
YYEMPTY
YYEOF
yylloc
包含 lookahead 标记语义值的变量 when
未设置为 或 。
不要在延迟语义操作中进行修改(请参阅 GLR 语义操作)。
请参阅操作。yychar
YYEMPTY
YYEOF
yylval
上一篇: 在动作中使用的特殊功能, 上一篇: 解析器C语言界面 [内容][索引]
Bison 生成的解析器可以打印诊断信息,包括错误和
跟踪消息。默认情况下,它们以英语显示。然而,野牛
还支持以用户的母语输出诊断。自
使此工作,用户应设置通常的环境变量。
参见 GNU gettext
实用程序中的用户视图。
例如,shell 命令 '' might
使用 UTF-8 将用户的区域设置设置为加拿大法语
编码。可用区域设置的确切集取决于用户的
安装。export LC_ALL=fr_CA.UTF-8
使用 Bison 生成的解析器的包的维护者启用 通过以下方式实现解析器输出的国际化 步骤。在这里,我们假设一个使用 GNU Autoconf 和 GNU 自动生成。
cp /usr/local/share/aclocal/bison-i18n.m4 m4/bison-i18n.m4
AM_GNU_GETTEXT
BISON_I18N
bison-i18n.m4configure
BISON_LOCALEDIR
YYENABLE_NLS
main
bindtextdomainbison-runtimebindtextdomain ("bison-runtime", BISON_LOCALEDIR);
通常,这会显示在包已有的任何其他调用之后。在这里,我们依靠
'' 定义为通过 .bindtextdomain
(PACKAGE, LOCALEDIR)
BISON_LOCALEDIRMakefile
main
BISON_LOCALEDIRDEFSAM_CPPFLAGSDEFS = @DEFS@ -DBISON_LOCALEDIR='"$(BISON_LOCALEDIR)"'
或:
AM_CPPFLAGS = -DBISON_LOCALEDIR='"$(BISON_LOCALEDIR)"'
autoreconf
当变量设置为 或 时,可以对令牌别名进行国际化:%define
parse.error
custom
detailed
%token '\n' _("end of line") <double> NUM _("number") <symrec*> FUN _("function") VAR _("variable")
语法的其余部分可以自由使用标记符号
() 或其别名 (),但不使用
国际化标记 ()。FUN
"function"
_("function")
如果至少有一个令牌别名是国际化的,则生成的解析器
将同时使用 和 ,必须定义
(参见 GNU gettext
实用程序中的程序员视图)。它们仅用于标记为要翻译的字符串别名。
换言之,即使您的目录具有
“function”,则使用N_
_
%token <symrec*> FUN "function" VAR _("variable")
“function”将在调试跟踪和错误消息中显示为未翻译。
除非由用户定义,否则将提供文件末标记 ,
“end of file”作为别名。如果用户
国际化代币。若要将其映射到另一个字符串,请使用:YYEOF
%token END 0 _("end of input")
Next: 错误恢复, Previous: 解析器 C 语言接口, 上一篇: Bison [Contents][Index]
当 Bison 读取令牌时,它会将它们与它们的 语义值。该堆栈称为解析器堆栈。推送 令牌传统上称为移位。
例如,假设中缀计算器的读数为 '',其中 ''来了。堆栈将有四个元素,每个令牌一个 这被转移了。1 + 5 *3
但是,堆栈并不总是具有每个读取的令牌的元素。什么时候 最后移动的标记和分组与 语法规则,它们可以根据该规则进行组合。这称为还原。这些令牌和分组在堆栈上被替换为 其符号是该规则的结果(左侧)的单个分组。 运行规则的操作是缩减过程的一部分,因为这 用于计算生成的分组的语义值。n
例如,如果中缀计算器的解析器堆栈包含以下内容:
1 + 5 * 3
下一个输入标记是换行符,然后是最后三个 元素可以通过以下规则减少到 15 个:
expr: expr '*' expr;
然后,堆栈仅包含以下三个元素:
1 + 15
此时,可以再进行一次减少,从而产生单个值 16. 然后可以移动换行符。
解析器尝试通过移位和缩减来减少整个输入 到其符号是语法的起始符号的单个分组 (请参阅语言和上下文无关语法)。
这种解析器在文献中被称为自下而上的解析器。
下一篇: Shift/Reduce 冲突,向上:Bison 解析器算法 [内容][索引]
Bison 解析器并不总是在 最后的标记和分组与规则匹配。这是因为这样的 简单的策略不足以处理大多数语言。相反,当 减少是可能的,解析器有时会“向前看”下一个 令牌,以便决定做什么。n
读取令牌时,它不会立即移动;首先,它成为 Lookahead 令牌,它不在堆栈上。现在解析器可以 在堆栈上执行一个或多个标记和分组的缩减,而 Lookahead 令牌仍然偏向一边。当不再减少时 如果发生,则 Lookahead 令牌将转移到堆栈上。这 并不意味着所有可能的削减都已完成;取决于 token 种类的 lookahead token,某些规则可能会选择延迟其 应用。
这是一个需要提前看的简单案例。这三条规则定义了 包含二进制加法运算符和后缀一元的表达式 阶乘运算符 (''),并允许使用括号进行分组。!
expr: term '+' expr | term ;
term: '(' expr ')' | term '!' | "number" ;
假设标记 '' 已被读取和移动;什么
应该做吗?如果以下标记是 '',则前三个
标记必须简化以形成 .这是唯一有效的
当然,因为移动“”会产生一系列符号,并且没有规则允许这样做。1 + 2)expr
)term ')'
如果以下标记是 '',则必须立即将其移位,以便
该 '' 可以简化为 .如果相反,
解析器在移位之前要减少,'' 将变成 .这样就不可能移动“”,因为
这样做会在堆栈上生成符号序列。没有规则允许该序列。!2 !term
1 + 2expr
!expr
'!'
lookahead 令牌存储在变量 中。它的语义
value 和 location(如果有)存储在变量 和 中。请参阅在操作中使用的特殊功能。yychar
yylval
yylloc
下一篇: 算子优先级, 上一篇: Lookahead Tokens, 上一篇: Bison Parser 算法 [内容][索引]
假设我们正在解析一种具有 if-then 和 if-then-else 的语言 语句,具有一对如下规则:
if_stmt: "if" expr "then" stmt | "if" expr "then" stmt "else" stmt ;
这里 和 是终端符号
特定关键字标记。"if"
"then"
"else"
当令牌被读取并成为前瞻令牌时,
堆栈的内容(假设输入有效)正好适合
根据第一条规则减少。但移动 也是合理的,因为这将导致最终减少秒
统治。"else"
"else"
这种情况,无论是轮班还是裁员都是有效的,是 称为 shift/reduce 冲突。Bison 旨在解决 这些冲突通过选择转移,除非另有指示 运算符优先级声明。要了解其中的原因,让我们 将其与其他替代方案进行对比。
由于解析器更喜欢移动 ,因此结果是附加
最里面的 if 语句的 else-子句,使这两个输入
等效:"else"
if x then if y then win; else lose; if x then do; if y then win; else lose; end;
但是,如果解析器选择尽可能减少而不是移动,则 结果是将 else-子句附加到最外层的 if 语句, 使这两个输入等效:
if x then if y then win; else lose; if x then do; if y then win; end; else lose;
冲突之所以存在,是因为所写的语法是模棱两可的:要么
解析简单的嵌套 if 语句是合法的。已建立的
按照惯例,这些歧义可以通过附加
else-子句添加到最里面的 if 语句;这就是野牛所取得的成就
通过选择转移而不是减少。(理想情况下,它会更干净
写一个明确的语法,但在这种情况下很难做到。
这种特殊的歧义首先出现在
Algol 60 被称为“悬空”歧义。else
为了帮助语法作者理解每个冲突的本质, 可以要求野牛生成“反例”。在本案中,它 实际上甚至通过展示字符串来证明语法是模棱两可的 具有两种不同的解析:
Example: "if" expr "then" "if" expr "then" stmt • "else" stmt Shift derivation if_stmt ↳ 3: "if" expr "then" stmt ↳ 2: if_stmt ↳ 4: "if" expr "then" stmt • "else" stmt Example: "if" expr "then" "if" expr "then" stmt • "else" stmt Reduce derivation if_stmt ↳ 4: "if" expr "then" stmt "else" stmt ↳ 2: if_stmt ↳ 3: "if" expr "then" stmt •
有关详细信息,请参阅反例的生成。
为了避免 Bison 发出关于可预测的、合法的移位/减少的警告
冲突时,可以使用声明。
只要 shift/reduce 冲突的数量,就不会有警告
正好是 ,如果出现
不同的数字。
请参阅禁止显示冲突警告。但是,我们没有
建议使用 (除了 ''!),作为
冲突的数量并不意味着它们是相同的。什么时候
可能,您应该使用 precedence 指令来修复
显式冲突(请参阅对非运算符使用优先级)。%expect n
n%expect
%expect 0
上述定义完全归咎于
冲突,但如果没有额外的冲突,冲突实际上不会出现
规则。这是一个完整的 Bison 语法文件,实际表现出来
冲突:if_stmt
%%
stmt: expr | if_stmt ;
if_stmt: "if" expr "then" stmt | "if" expr "then" stmt "else" stmt ;
expr: "identifier" ;
Next: 上下文相关优先级, Previous: Shift/Reduce conflicts, Up: Bison Parser Algorithm [Contents][Index]
出现移位/减少冲突的另一种情况是算术 表达 式。在这里,移位并不总是首选的解决方案;这 运算符优先级的 Bison 声明允许您指定何时 转变和何时减少。
考虑以下模棱两可的语法片段(模棱两可,因为 输入 '' 可以通过两种不同的方式进行解析):1 - 2 * 3
expr: expr '-' expr | expr '*' expr | expr '<' expr | '(' expr ')' … ;
假设解析器已经看到了标记 ''、'' 和 ''; 它应该通过减法运算符的规则来减少它们吗?它 取决于下一个令牌。当然,如果下一个标记是“”,我们 必须减少;移位是无效的,因为没有一个规则可以减少 令牌序列 '' 或任何以该序列开头的内容。但是,如果 下一个标记是 '' 或 '',我们有一个选择:要么 移位或减少将允许解析完成,但 不同的结果。1-2)- 2 )*<
要决定野牛应该做哪一个,我们必须考虑结果。如果 下一个运算符令牌被移动,然后必须减少它 首先,为了允许另一个机会来减少差异。 结果是(实际上)''。另一方面 手,如果减法在移位前减少,结果 是 ''。显然,选择换档或 reduce 应取决于运算符的相对优先级 '' 和 : '' 应该首先移动,但不要 ‘’.op1 - (2 op 3)op(1 - 2) op 3-op*<
诸如“”之类的输入呢?这应该是 '' 还是应该是 ''?对于大多数人 运算符我们更喜欢前者,这称为左关联。 后一种选择,即正确的关联,是可取的 赋值运算符。左关联或右关联的选择是一个 解析器在堆栈时选择移位还是减少的问题 包含 '',并且 Lookahead 标记为 '': Moving 使右联想。1 - 2 - 5(1 - 2) - 51 - (2 - 5)1 - 2-
Bison 允许您使用运算符优先级指定这些选项
声明和 .每个这样的声明
包含令牌列表,这些令牌是优先级和
正在宣布关联性。宣言使所有
那些运算符左关联,声明使
他们右联想。第三种选择是 ,它
声明在“在
行“。
最后一种选择,只允许定义
优先级,根本没有关联性。因此,任何
仍然存在的与关联相关的冲突将报告为
编译时错误。该指令创建运行时
错误:以关联方式使用运算符是语法错误。这
指令会创建编译时错误:运算符可能涉及与关联性相关的冲突,这与
语法作者的期望。%left
%right
%left
%right
%nonassoc
%precedence
%nonassoc
%precedence
不同运算符的相对优先级由 声明它们的顺序。第一个优先级/关联性 文件中的声明声明其 优先级最低,下一个此类声明声明运算符 其优先级稍高,依此类推。
由于 POSIX Yacc 只定义了 、 和 ,它们都定义了优先级和关联性,因此很少
需要注意的是,没有
定义关联性。然而,有时,当试图解决
冲突,优先就足够了。在这种情况下,使用 、 或 可能隐藏未来 (关联性
相关)的冲突将保持隐藏状态。%left
%right
%nonassoc
%left
%right
%nonassoc
可以解决悬而未决的歧义(参见 Shift/Reduce Conflicts)
明确地。此 shift/reduce 冲突发生在以下情况下:
其中句点表示当前解析状态:else
if e1 then if e2 then s1 • else s2
冲突涉及规则 '' 的减少,默认情况下,该规则的优先级是其最后一个标记的优先级
(),以及令牌的移动。通常
消除歧义(将 附加到最接近的),
移位必须是首选,即 的优先级必须为
高于 。但预计两者都不会参与其中
在与关联性相关的冲突中,可以按如下方式指定。IF expr THEN
stmtTHEN
ELSE
else
if
ELSE
THEN
%precedence THEN %precedence ELSE
一元减号是另一个典型的例子,其中关联性通常是
过度指定,请参阅 Infix Notation Calculator: calc
。该指令是
传统上用于声明 的优先级,哪个更
比需要的要多,因为它还定义了它的关联性。虽然这是无害的
在传统示例中,谁知道将来会如何使用
语法的演变...%left
NEG
NEG
Next: Precedence 的工作原理, Previous: 仅指定 Precedence, Up: 运算符 Precedence [Contents][Index]
在我们的示例中,我们需要以下声明:
%left '<' %left '-' %left '*'
在一个更完整的示例中,我们也支持其他运算符,我们
将以同等优先级的组宣布它们。例如,是
声明用:'+'
'-'
%left '<' '>' '=' "!=" "<=" ">=" %left '+' '-' %left '*' '/'
Next: 使用非运算符的优先级, Previous: 优先级示例, Up: 运算符优先级 [Contents][索引]
优先级声明的第一个效果是分配优先级 级别到声明的终端符号。第二个效果是分配 特定规则的优先级:每个规则的优先级来自 组件中提到的最后一个终端符号。(您也可以 显式指定规则的优先级。请参阅上下文相关优先级。
最后,通过比较优先级来解决冲突 的规则与 lookahead 令牌的规则一起考虑。如果 代币的优先级更高,选择是转移。如果规则是 优先级更高,选择是减少。如果他们有相等的 优先级,选择是基于其关联性做出的 优先级。由(请参阅调用 Bison)制作的详细输出文件说明了每个冲突是如何发生的 解决。-v
并非所有规则和所有令牌都具有优先权。如果规则或 Lookahead 令牌没有优先级,则默认值为 Shift。
Previous: Precedence 的工作原理, Up: Operator Precedence [Contents][Index]
正确使用优先级和关联性指令可以帮助修复
不涉及类似算术运算符的 shift/reduce 冲突。为
例如,“悬空”问题(请参阅移位/减少冲突)可以是
以两种不同的方式优雅地解决了。else
在本例中,冲突在于代币愿意
要转移,规则“”,询问
用于减少。默认情况下,规则的优先级是其最后一个规则的优先级
token,这里,所以冲突会得到适当的解决
通过给出高于 的优先级,对于
实例如下:"else"
if_stmt: "if" expr "then" stmt"then"
"else"
"then"
%precedence "then" %precedence "else"
或者,您可以为两个令牌提供相同的优先级,在这种情况下 关联性用于解决冲突。为了保留换档动作, 使用正确的关联性:
%right "then" "else"
然而,这两种解决方案都不是完美的。由于 Bison 目前不提供, “作用域”优先级,两者都强制你声明优先级 这些关键字相对于其他运算符的语法。 因此,您不会被告知新的冲突,而不是被警告 的(例如,由于“' 模棱两可:“或”“?),冲突将已经”修复“。if test then 1 else 2 + 3if test then 1 else (2 + 3)(if test then 1 else 2) + 3
下一篇: 解析器状态, 上一篇: 算子优先级, 上一篇: Bison 解析器算法 [内容][索引]
通常,运算符的优先级取决于上下文。这听起来 乍一看很古怪,但确实很常见。例如,减号 符号通常具有非常高的一元运算符的优先级,并且 作为二进制运算符的优先级略低(低于乘法)。
Bison 优先声明
对于给定的令牌,只能使用一次;所以令牌有
以这种方式声明的只有一个优先级。对于上下文相关
优先级,您需要使用附加机制:规则修饰符。%prec
修饰符声明特定规则的优先级
指定一个终端符号,其优先级应用于该规则。
该符号不必以其他方式出现在规则中。这
修饰符的语法是:%prec
%prec terminal-symbol
它写在规则的组成部分之后。其作用是 为规则分配 的优先级 ,覆盖 以普通方式为它推导出的优先级。这 然后,更改的规则优先级会影响涉及该规则的冲突 已解析(请参阅运算符优先级)。terminal-symbol
以下是解决一元减号问题的方法。首先,声明
名为 的虚构终端符号的优先级。那里
不是这种类型的标记,但该符号用于代表其
优先:%prec
UMINUS
… %left '+' '-' %left '*' %left UMINUS
现在,可以在特定规则中使用 的优先级:UMINUS
exp: … | exp '-' exp … | '-' exp %prec UMINUS
下一篇: 减少/减少冲突, 上一篇: 上下文相关优先级, 上一篇: Bison 解析器算法 [内容][索引]
该函数是使用有限状态机实现的。
在解析器堆栈上推送的值不仅仅是令牌种类代码;他们
表示终端和非终端符号的整个序列,位于 或
靠近堆栈的顶部。当前状态收集所有信息
关于与决定下一步做什么相关的先前输入。yyparse
每次读取 lookahead 令牌时,当前解析器状态以及 在表中查找 Lookahead 令牌的类型。此表条目可以 说,“移动前瞻令牌。在这种情况下,它还指定了新的 解析器状态,它被推送到解析器堆栈的顶部。或者它可以 说,“使用规则编号减少。这意味着一定数量的 的令牌或分组从堆栈的顶部移除,并替换为 一个分组。换言之,该数量的状态是从 堆栈,并推送一个新状态。n
还有另一种选择:该表可以说 lookahead 令牌 在当前状态下是错误的。这会导致错误处理开始 (请参阅错误恢复)。
如果有两个或多个适用的规则,则会发生减少/减少冲突 到相同的输入序列。这通常表示存在严重错误 在语法中。
例如,这里是定义序列的错误尝试
零个或多个分组。word
sequence: %empty { printf ("empty sequence\n"); } | maybeword | sequence word { printf ("added word %s\n", $2); } ;
maybeword: %empty { printf ("empty maybeword\n"); } | word { printf ("single word %s\n", $1); } ;
错误是模棱两可的:正如反例生成所证明的那样
(请参阅反例的生成),有多种方法可以将单个解析为 .它可以通过第二条规则简化为 a,然后简化为 a。
或者,什么都不可以通过第一条规则简化为 a,这可以与使用 3 条规则结合使用 。word
sequence
maybeword
sequence
sequence
word
sequence
还有不止一种方法可以将 nothing-at-all 简化为 .这可以通过第一条规则直接完成,
或间接通过,然后是第二条规则。sequence
maybeword
你可能会认为这是一个没有区别的区别,因为它 不会更改任何特定输入是否有效。但它确实如此 影响运行的操作。一个解析顺序运行第二个规则的 行动;另一个运行第一条规则的操作和第三条规则的操作。 在此示例中,程序的输出发生了变化。
Bison 通过选择使用以下规则来解决减少/减少冲突
在语法中首先出现,但依赖这个是非常危险的。每
必须研究减少/减少冲突,并且通常可以消除冲突。这是
正确的定义方式:sequence
sequence: %empty { printf ("empty sequence\n"); } | sequence word { printf ("added word %s\n", $2); } ;
这是另一个导致 reduce/reduce 冲突的常见错误:
sequence: %empty | sequence words | sequence redirects ;
words: %empty | words word ;
redirects: %empty | redirects redirect ;
此处的目的是定义一个可以包含 or 分组的序列。和 的各个定义没有错误,但
三者结合在一起会产生微妙的歧义:即使是空输入也可以解析
以无限多种方式!word
redirect
sequence
words
redirects
考虑一下:什么都不可能是.或者它可以是连续两个,或者三个,或任何数字。它同样可以是一个、两个或任何数字。或者它可以是 a 后面跟着 3 和 另一个 .等等。words
words
redirects
words
redirects
words
以下是纠正这些规则的两种方法。首先,使其成为一个单一的级别 的序列:
sequence: %empty | sequence word | sequence redirect ;
其次,要防止 a 或 a 为空:words
redirects
sequence: %empty | sequence words | sequence redirects ;
words: word | words word ;
redirects: redirect | redirects redirect ;
然而,这个提议引入了另一种模棱两可!输入
'' 可以解析为由两个组成的单个
''s,或作为两个 one-(同样用于 /)。然而,这种模棱两可现在是一个
转移/减少冲突,因此现在可以优先解决它
指令。word wordwords
wordword
words
redirect
redirects
为了简化问题,我们将继续使用和成为令牌:和。word
redirect
"word"
"redirect"
要优先选择最长的,必须解决令牌和规则 '' 之间的冲突
作为转变。为此,我们使用与上面相同的技术,请参阅对非运算符使用优先级。一个解决方案
依赖于优先级:用于为
统治:words
"word"
sequence: sequence words%prec
%precedence "word" %precedence "sequence" %%
sequence: %empty | sequence word %prec "sequence" | sequence redirect %prec "sequence" ;
words: word | words "word" ;
另一种解决方案依赖于关联性:同时提供令牌和 使用相同的优先级进行规则,但使它们具有正确的关联性:
%right "word" "redirect" %%
sequence: %empty | sequence word %prec "word" | sequence redirect %prec "redirect" ;
有时,可能会发生看起来没有必要的减少/减少冲突。 下面是一个示例:
%% def: param_spec return_spec ','; param_spec: type | name_list ':' type ;
return_spec: type | name ':' type ;
type: "id";
name: "id"; name_list: name | name ',' name_list ;
似乎只能用一个标记来解析这种语法
前瞻:读取 A 时,如果后面跟着逗号或冒号,则 A 为 A,如果后面跟着另一个逗号或冒号,则为 A。换句话说,这个语法就是 LR(1)。然而野牛
找到一个 reduce/reduce 冲突,为此生成反例
(见反例的生成)会找到一个不统一的例子。param_spec
"id"
name
type
"id"
这是因为 Bison 默认不处理所有 LR(1) 语法,
由于历史原因。
在这种语法中,有两个上下文,即在开头的 an 之后
的 a 和 同样在 a 的开头 ,它们非常相似,以至于 Bison 认为它们是
相同。
它们看起来很相似,因为同一组规则是
active—减少到 a 的规则和减少到
一个。Bison无法在处理的那个阶段确定
规则将要求在两者中使用不同的前瞻令牌
contexts,因此它为它们都创建了一个解析器状态。结合
这两个上下文稍后会导致冲突。在解析器术语中,这
occurrence 表示语法不是 LALR(1)。"id"
param_spec
return_spec
name
type
对于许多实用语法(特别是那些属于非 LR 的语法)(1) 类),LALR(1)的局限性导致了不仅仅是 神秘的减少/减少冲突。解决所有这些问题的最佳方法 是选择不同的解析器表构造算法。也 IELR(1) 或规范 LR(1) 就足够了,但前者更有效 并且在开发过程中更易于调试。请参阅 LR 表构造,了解 详。
如果您希望绕过 LALR(1) 的局限性,则可以解决
通常可以通过识别两个解析器状态来解决神秘的冲突
被混淆了,并添加了一些东西来使它们看起来
不同。在上面的示例中,将一个规则添加到如下所示可使问题消失:return_spec
… return_spec: type | name ':' type | "id" "bogus" /* This rule is never used. */ ;
这纠正了这个问题,因为它引入了
在 开头的上下文中的其他活动规则。此规则在相应的上下文中未处于活动状态
在 中,因此两个上下文接收不同的解析器状态。
只要令牌从来不是由 生成的 ,
添加的规则无法更改实际输入的解析方式。"id"
return_spec
param_spec
"bogus"
yylex
在这个特定示例中,还有另一种解决问题的方法:
重写规则以直接使用
而不是通过 .这也导致两者混淆
上下文具有不同的活动规则集,因为 的 激活更改的规则 而不是 的 。return_spec
"id"
name
return_spec
return_spec
name
param_spec: type | name_list ':' type ;
return_spec: type | "id" ':' type ;
有关 LALR(1) 解析器和解析器生成器的更详细阐述,请参见 DeRemer 1982。
下一篇: 广义 LR (GLR) 解析, 上一篇: 神秘冲突, 上一篇: Bison 解析器算法 [内容][索引]
Bison 基于 LR 的解析器的默认行为主要用于
历史原因,但这种行为往往不稳健。例如,在
在上一节中,我们讨论了可能存在的神秘冲突
由 LALR(1) 生成,这是 Bison 的默认解析器表构造算法。
另一个例子是 Bison 的指令,
它指示生成的解析器产生详细的语法错误
消息,有时可能包含不正确的信息。%define parse.error verbose
在本节中,我们将探讨 Bison 的几个现代功能,这些功能可让您 调整生成的基于 LR 的解析器的基本方面。一些 这些功能可以轻松消除上述缺点。 其他的可能纯粹有助于理解您的解析器。
由于历史原因,Bison 默认构造 LALR(1) 解析器表。 但是,LALR 不具备 LR 的全部语言识别能力。 因此,使用 LALR 解析器表的解析器的行为通常 神秘。我们在《神秘的冲突》中展示了这种效应的简单例子。
正如我们在该示例中还演示的那样,传统方法 消除这种神秘的行为就是重组语法。 不幸的是,正确地做到这一点通常很困难。此外,仅仅 发现 LALR 在解析器中导致神秘行为可能是 也很难。
幸运的是,Bison提供了一种简单的方法来消除这种可能性
完全是神秘的行为。你只需要激活一个更强大的
使用指令的解析器表构造算法。%define lr.type
指定 LR(1) 系列中解析器表的类型。接受的 的值为:type
lalr
(默认)ielr
canonical-lr
例如,若要激活 IELR,可以向 语法文件:
%define lr.type ielr
例如,在《神秘的冲突》中,神秘的 然后消除冲突,因此无需投入时间 理解冲突或重组语法以修复它。如果 在未来的发展过程中,语法的发展使得所有神秘的 仅使用 LALR 的行为就会消失,您不必担心 继续使用 IELR 将导致不必要的大型解析器表。 也就是说,IELR 在 LALR 时生成 LALR 表(使用确定性解析 algorithm)足以支持 LR。因此,通过在语法开发开始时启用 IELR,您可以 安全、彻底地消除了考虑 LALR 缺点的需要。
虽然 IELR 几乎总是更可取的,但在某些情况下,LALR 或者 Knuth 描述的规范 LR 解析器表(参见 Knuth 1965)可以 有用。在这里,我们总结了每个解析器表的相对优势 Bison中的构造算法:
至少在两种情况下,LALR 是值得的:
使用 GLR 解析器时(请参阅编写 GLR 解析器),如果不解析任何
静态冲突(例如,与 或 冲突),
然后
解析器探索任何给定输入的所有潜在解析。在这种情况下,
解析器表构造算法的选择保证不会改变
解析器接受的语言。LALR 解析器表是最小的
Bison 目前可以构建的解析器表,因此它们可能更可取。
然而,一旦你开始静态地解决冲突,GLR就会表现得像
更像是句法上下文中的确定性解析器,其中那些
出现冲突,因此 IELR 或规范 LR 都有助于
避免LALR的神秘行为。%left
%precedence
在开发过程中,偶尔会出现一种特别畸形的语法,带有 反复出现的主要缺陷可能会严重阻碍 IELR 或规范 LR 解析器 表构造算法。LALR 可以成为构建解析器的快速方法 表,以便调查此类问题,同时忽略更微妙的问题 与 IELR 和规范 LR 的区别。
IELR(不足消除 LR)是一种最小的 LR 算法。也就是说,给定 任何语法(LR 或非 LR)、使用 IELR 或规范 LR 解析器表的解析器 始终接受完全相同的句子集。但是,与 LALR 一样,IELR 在解析器表构造期间合并解析器状态,以便 解析器状态通常比规范 LR 小一个数量级。 更重要的是,因为规范 LR 的额外解析器状态可能包含 在非 LR 语法的情况下,重复冲突的数量 因为 IELR 通常也少一个数量级。这种效果可以 显著降低语法开发的复杂性。
虽然效率低下,但规范的 LR 解析器表可能是一种有趣的方法
探索语法,因为它们具有 IELR 和 LALR 表的属性
不要。也就是说,如果未使用,并且默认减少为
left disabled(请参阅默认减少),然后,对于每个左侧上下文
每个规范的 LR 状态,该状态接受的令牌集是
保证是语法上可接受的确切标记集
那左边的背景。这样看来,规范 LR 的一个优势
生产中的解析器是,在上述约束下,它们是
保证在不执行的情况下尽快检测到语法错误
任何不必要的减少。但是,使用 LAC 的 IELR 解析器也是
能够在不牺牲或
默认减少。有关 LAC 的详细信息和一些注意事项,请参阅 LAC。%nonassoc
%nonassoc
更详细地阐述 LALR 解析器中的神秘行为 以及 IELR 的好处,参见 Denny 2008 和 Denny 2010 November。
在构建解析器表后,Bison 使用 每个解析器状态中的最大前瞻集。要减小 解析器状态,传统的 Bison 行为是删除该 lookahead 集和 将该缩减指定为默认分析器操作。这样的减少 称为默认减少。
默认缩减影响的不仅仅是分析器表的大小。他们 还会影响解析器的行为:
yylex
一致状态是只有一个可能的解析器的状态
行动。如果该操作是缩减并编码为默认值
还原,则该一致状态称为默认状态。
在达到默认状态后,Bison 生成的解析器不会打扰
invoke 在执行缩减之前获取下一个令牌。
换言之,是否在一致状态下启用默认减少
确定 Bison 生成的解析器调用
token:当它到达输入中的该令牌时立即或当它
最终需要该令牌作为前瞻,以确定下一个
解析器操作。传统上,默认减少是启用的,因此
Parser 表现出后一种行为。yylex
yylex
在以下情况下,默认状态的存在是一个重要的考虑因素
设计和语法文件。也就是说,如果行为可以影响或受语义行为的影响
与默认状态的减少相关联,然后是
在这些减少之后的下一次调用非常重要。
例如,语义操作可能会弹出一个作用域堆栈,用于确定要返回的令牌。因此,延迟可能是必要的
以确保不会在作用域中查找下一个令牌
应该已经被视为已关闭。yylex
yylex
yylex
yylex
yylex
当解析器通过调用 获取新令牌时,它会检查
在当前解析器状态下是否存在针对该令牌的操作。这
解析器检测到语法错误当且仅当 (1) 没有操作
对于该令牌或 (2) 该令牌的操作是错误操作(由于
的使用)。但是,如果默认减少
该状态(可能是也可能不是默认状态),那么它是
条件 1 不可能存在。也就是说,所有令牌都有一个操作。
因此,解析器有时无法检测到语法错误,直到它达到
后来的状态。yylex
%nonassoc
虽然默认减少永远不会导致解析器在语法上接受
不正确的句子,语法错误检测的延迟可能会有意想不到的
对解析器行为的影响。但是,可能会导致延迟
无论如何,通过解析器状态合并和使用 ,它可以
由另一个 Bison 功能 LAC 修复。我们讨论延迟的影响
语法错误检测和 LAC 将在下一节中详细介绍(请参阅 LAC)。%nonassoc
对于规范 LR,Bison 默认启用的唯一默认减少
是 accept 操作,它只出现在 Accept 状态中,它具有
没有其他操作,因此是默认状态。但是,默认接受
操作不会延迟任何调用或语法错误检测
因为 Accept 操作结束分析。yylex
对于 LALR 和 IELR,Bison 在几乎所有州都通过以下方式实现默认减少
违约。只有两个例外。首先,有转变的州
对令牌的操作没有默认减少,因为
延迟的语法错误检测可能会阻止令牌
从在那种状态下被转移。但是,解析器状态合并可以
无论如何都会引起相同的效果,并且 LAC 在这两种情况下都会修复它,所以将来
当 LAC 被激活时,Bison 的版本可能会删除此异常。第二
GLR 解析器不会将默认减少记录为前瞻操作
存在冲突的令牌。在这种情况下,正确的操作是
而是拆分解析。error
error
要调整哪些州启用了默认减少,请使用指令。%define lr.default-reduction
指定允许包含缺省减少的状态类型。 可接受的值为:where
most
(LALR 和 IELR 的默认值)consistent
accepting
(规范 LR 的默认值)Canonical LR、IELR 和 LALR 可能会遇到一些问题 遇到语法错误。首先,解析器可能会执行其他 在发现语法错误之前减少解析器堆栈。这样 缩减可以执行意外的用户语义操作,因为 它们基于无效的令牌,并导致错误恢复开始 在与无效标记所在的语法上下文不同的语法上下文中 遇到。其次,当启用详细错误消息时(请参阅错误报告),语法错误消息中的预期令牌列表可以同时 包含无效令牌并省略有效令牌。
造成上述问题的罪魁祸首是,默认减少
处于不一致状态(请参阅默认减少)和解析器状态
合并。由于 IELR 和 LALR 合并解析器状态,因此它们受到的影响最大。
Canonical LR 只有在使用或默认时才会受到影响
对于不一致的状态,将启用减少。%nonassoc
%nonassoc
LAC(Lookahead Correction)是解析算法中的一种新机制
这解决了规范 LR、IELR 和 LALR 的这些问题,而无需
牺牲、默认减少或状态合并。您可以
使用指令启用 LAC。%nonassoc
%define parse.lac
启用 LAC 以改进语法错误处理。
none
(默认)full
此功能目前仅适用于 C 和 C++ 中的确定性解析器。
从概念上讲,LAC 机制是直截了当的。每当解析器 从扫描程序获取新令牌,以便它可以确定下一个 解析器操作,它会立即暂停正常解析并执行 使用普通解析器状态堆栈的临时副本进行探索性解析。 在此探索性分析期间,分析器不执行用户语义 行动。如果探索性解析达到移位操作,则正常解析 然后在普通解析器堆栈上恢复。如果探索性分析达到 错误 相反,分析器会报告语法错误。如果 verbose 语法 错误消息启用后,解析器必须发现 预期的令牌,因此它对每个令牌执行单独的探索性分析 在语法中。
LAC的使用有一个微妙之处。也就是说,当在一致的 具有默认约简的解析器状态,解析器不会尝试获取 来自扫描程序的令牌,因为不需要提前查看来确定 下一个分析器操作。因此,是否在 一致状态(请参阅默认减少)会影响分析器的速度 检测语法错误:当它到达错误时立即 token,或者当它最终需要该令牌作为前瞻时 确定下一个分析器操作。后一种行为可能更多 直觉,所以Bison目前没有提供实现前一种行为的方法 而默认减少在一致状态下启用。
因此,当 LAC 在使用时,对于是否启用某些固定决定 默认减少 在一致状态下,规范 LR 和 IELR 的行为几乎 句法上可接受和句法上完全相同 不可接受的输入。虽然 LALR 仍然不支持完整的 规范 LR 和 IELR 的语言识别能力,LAC 至少能够实现 LALR 的语法错误处理,以正确反映 LALR 的 语言识别能力。
使用 LAC 时需要考虑一些注意事项:
IELR 加 LAC 相对于规范 LR 确实有一个缺点。一些 Bison 生成的解析器可以无限循环。LAC 不固定无限 分析在遇到语法错误和检测之间发生的循环 它,但有时会启用规范 LR 或禁用默认减少 确实如此。
出于国际化的考虑,Bison 生成的解析器 限制他们愿意在 详细语法错误消息。如果预期令牌的数量超过该数量 limit,则该列表只是从消息中删除。启用 LAC 可以 增加列表的大小,从而导致分析器删除它。之 当然,删除列表比报告不正确的列表要好。
由于 LAC 需要执行两次许多解析操作,因此它可以具有 绩效损失。但是,并非所有分析操作都必须执行 两次。具体来说,在一系列默认减少期间,一致性 状态和移位操作,解析器永远不必启动探索性 解析。此外,解析中最耗时的任务通常是 文件 I/O、扫描程序执行的词法分析以及用户的 语义操作,但这些都不是在探索期间执行的 解析。最后,在探索期间使用的临时堆栈的底部 parse 是指向普通解析器状态堆栈的指针,因此堆栈是 从未物理复制过。根据我们的经验,LAC 的性能损失 事实证明,对于实用语法来说,这是微不足道的。
虽然 LAC 算法共享已在 解析器社区多年来,有关介绍 LAC 的出版物,请参阅 Denny 2010 May。
如果不存在从解析器的开始状态到 一些状态,那么野牛认为是无法到达的 状态。在解决冲突期间,如果 Bison 禁用导致该移位操作从前置状态。ss
默认情况下,Bison 会在冲突后从解析器中删除无法访问的状态 分辨率,因为它们在生成的解析器中是无用的。然而 在尝试理解 解析器和语法之间的关系。
请求 Bison 允许无法访问的状态保留在解析器表中。 必须是布尔值。默认值为 。valuefalse
需要考虑以下几点:
无法访问的状态可能包含冲突,并且可能使用任何 其他状态。因此,保持不可访问状态可能会诱发以下警告: 与解析器的行为无关,并且可能会消除以下警告: 相关。当然,警告的变化实际上可能与 解析表分析想要保持无法访问的状态,所以这个 行为可能会在未来的 Bison 版本中保留。
虽然 Bison 能够删除无法访问的状态,但不能保证 删除其他类型的无用状态。具体来说,当 Bison 禁用 减少冲突解决过程中的操作,某些转到操作可能会变成 无用,因此一些额外的状态可能变得无用。如果野牛是 要计算哪些 goto 操作是无用的,然后禁用这些操作, 它可以将此类状态识别为无法访问,然后删除这些状态。 但是,Bison 不会计算哪些 goto 操作是无用的。
下一篇: 内存管理,如何避免内存耗尽, 上一篇: 调优 LR, 上一篇: Bison 解析器算法 [内容][索引]
Bison 生成唯一选择的确定性解析器 何时减少以及应用哪种减少 基于前面输入的摘要和一个额外的 Lookahead 标记。 因此,普通的野牛会处理 上下文无关的语言。 模棱两可的语法,因为它们具有多个可能的字符串 从这个意义上说,还原序列不能有确定性解析器。 对于需要多个符号的语言也是如此 展望未来,因为解析器缺乏制作 必须在 shift/reduce 解析器中做出决定。 最后,如前所述(见神秘冲突), 在某些语言中,Bison 默认选择如何 总结到目前为止看到的输入会丢失必要的信息。
当您在语法文件中使用 '' 声明时, Bison 生成一个使用不同算法的解析器,称为 广义 LR(或 GLR)。野牛GLR 解析器使用相同的基本 解析为普通 Bison 解析器的算法,但行为 在不存在转移/减少冲突的情况下,情况有所不同 已通过优先级规则解析(请参阅运算符优先级)或 减少/减少冲突。当 GLR 解析器遇到这样的 情况,它 有效地拆分为多个解析器,每个解析器对应一个可能的解析器 移位或减少。然后,这些解析器像往常一样进行,消耗 令牌在锁步中。某些堆栈可能会遇到其他冲突 并进一步拆分,结果是不是一系列状态, Bison GLR 解析堆栈实际上是一棵状态树。%glr-parser
实际上,每个堆栈都表示对正确解析内容的猜测 是。额外的输入可能表明猜测是错误的,在这种情况下 相应的堆栈会静默消失。否则,语义 每个堆栈中生成的操作将被保存,而不是被执行 马上。当堆栈消失时,其保存的语义操作永远不会 被处决。当减少导致两个堆栈变得相等时, 它们的语义操作集都保存在以下状态中: 减少的结果。我们说两个堆栈是等价的 当它们都代表相同的状态序列时, 每对应的状态代表一个 生成输入标记的相同段的语法符号 流。
每当解析器从具有多个 状态为具有一个,它将恢复为正常的确定性解析 算法,在解析并执行保存的操作后。 在此转换中,堆栈上的某些状态将具有语义 值是可能操作的集合(实际上是多集)。这 Parser 尝试通过首先找到其规则的操作来选择其中一个操作 具有最高的动态优先级,由 '' 设置 声明。否则,如果替代操作不是由 优先级,但两者声明了相同的合并函数 规则由''声明, Bison 解析并计算两者,然后调用 结果。否则,它会报告歧义。%dprec%merge
可以对 GLR 解析树使用数据结构 允许在线性时间内处理任何 LR(1) 语法(在 输入的大小),任何明确(不一定 LR(1)) 语法 二次最坏情况时间,以及任何一般(可能模棱两可) 立方最坏情况下的上下文无关语法。然而,野牛目前 使用更简单的数据结构,该结构需要的时间与 输入长度乘以任何 输入的前缀。因此,确实模棱两可或不确定 语法可能需要指数级的时间和空间来处理。太糟糕了 然而,行为示例通常没有实际意义。 通常,语法中的非确定性是局部的——解析器是“在 怀疑“一次只针对几个令牌。因此,当前数据 结构一般应足够。在 LR(1) 部分 特别是语法,它只比 确定性 LR(1) Bison 解析器。
有关 GLR 解析器的更详细阐述,请参阅 Scott 2000。
上一篇: 广义 LR (GLR) 解析, 上一篇: Bison 解析器算法 [内容][索引]
如果移动了太多令牌,则 Bison 解析器堆栈可能会耗尽内存,并且
不减少。发生这种情况时,解析器函数调用并返回 2。yyparse
yyerror
因为 Bison 解析器的堆栈越来越大,达到了上限 通常是由于使用右递归而不是左递归 递归,请参阅递归规则。
通过定义宏,您可以控制
解析器堆栈可以在内存耗尽之前成为。定义
值为整数的宏。此值是最大数字
在溢出之前可以转移(而不是减少)的令牌。YYMAXDEPTH
不一定分配允许的堆栈空间。如果指定
large 值 ,解析器通常分配一个小
首先堆叠,然后根据需要分阶段使其变大。这
增加分配是自动且无提示的。因此
你不需要仅仅为了节省而痛苦地渺小
不需要太多堆栈的普通输入的空间。YYMAXDEPTH
YYMAXDEPTH
但是,不要允许值太大,以至于
计算堆栈大小时可能会发生算术溢出
空间。另外,不允许小于 .YYMAXDEPTH
YYMAXDEPTH
YYINITDEPTH
如果未定义 ,则 的默认值为
10000.YYMAXDEPTH
您可以通过定义
宏设置为正整数。对于确定性
解析器 在 C 中,此值必须是编译时常量
除非您假设使用 C99 或其他目标语言或编译器
这允许可变长度的数组。默认值为 200。YYINITDEPTH
不允许大于 。YYINITDEPTH
YYMAXDEPTH
您可以生成包含C++用户代码的确定性解析器 默认 (C) 框架,以及来自 C++ 框架(请参阅 C++ 解析器)。但是,如果您确实使用默认骨架并希望允许 解析堆栈要增长,注意不要使用语义类型或位置 需要非平凡复制构造函数的类型。C 骨架旁路 这些构造函数在将数据复制到新的、更大的堆栈时。
下一篇: 处理上下文依赖, 上一篇: Bison 解析器算法, 上一篇: Bison [内容][索引]
让程序以语法终止通常是不可接受的 错误。例如,编译器应充分恢复以解析 输入文件的其余部分并检查其是否有错误;计算器应该接受 另一种表达方式。
在一个简单的交互式命令解析器中,每个输入都是一行,它可以
足以允许在错误时返回 1 并具有
发生这种情况时,调用方将忽略输入行的其余部分(然后再次调用)。但这对于编译器来说是不够的,因为它
忘记了导致错误的所有语法上下文。语法错误
在编译器输入的函数深处不应导致编译器
将以下行视为源文件的开头。yyparse
yyparse
您可以通过将规则写入
识别特殊令牌。这是一个终端符号
始终定义(您不需要声明它)并保留错误
处理。Bison 解析器在
发生语法错误;如果已提供识别此令牌的规则
在当前上下文中,解析可以继续。error
error
例如:
stmts: %empty | stmts '\n' | stmts exp '\n' | stmts error '\n'
此示例中的第四条规则表示错误后跟换行符
对任何 .stmts
如果在 ?这
严格解释的错误恢复规则适用于精确序列
的 a、an 和 换行符。如果在
中间,可能会有一些额外的代币
和堆栈上的子表达式在最后一个 和
将是下一个换行符之前要读取的标记。所以规则不是
以普通方式适用。exp
stmts
error
exp
stmts
但是野牛可以通过丢弃部分
语义上下文和部分输入。首先,它丢弃状态和
对象,直到它恢复到令牌可接受的状态。(这意味着子表达式
已经解析的被丢弃,回到最后完成。在
在这一点上,令牌可以移动。然后,如果旧的
解析器读取 Lookahead 令牌不可接受接下来移动
令牌并丢弃它们,直到找到可接受的令牌。在
在此示例中,Bison 读取和丢弃输入,直到下一个换行符,以便
第四条规则可以适用。请注意,丢弃的符号是可能的来源
的内存泄漏,请参阅释放丢弃的符号,了解回收此内容的方法
记忆。error
stmts
error
语法中错误规则的选择是策略的选择 错误恢复。一个简单而有用的策略是简单地跳过其余部分 如果检测到错误,则为当前输入行或当前语句:
stmt: error ';' /* On error, skip until ';' is read. */
恢复到匹配的 close 分隔符也很有用 已解析的 open-delimiter。否则, close-delimiter 可能会看起来不匹配,并生成另一个, 虚假错误消息:
primary: '(' expr ')' | '(' error ')' … ;
错误恢复策略必然是猜测。当他们猜错时,
一个语法错误往往会导致另一个语法错误。在上面的示例中,错误
恢复规则猜测错误是由于 .假设在
中间的有效 .错误恢复规则从以下位置恢复后
第一个错误,另一个语法错误将立即被发现,因为
伪分号后面的文本也是无效的。stmt
stmt
stmt
为了防止错误消息的大量涌现,解析器不会输出任何错误 在第一个语法错误之后不久发生的另一个语法错误的消息;只 连续三个输入令牌成功转移后,将 错误消息恢复。
请注意,接受令牌的规则可能具有操作,只是
就像任何其他规则一样。error
通过在操作中使用宏,可以使错误消息立即恢复。如果在错误规则的操作中执行此操作,则不会
错误消息将被禁止显示。此宏不需要参数;
'' 是有效的 C 语句。yyerrok
yyerrok;
出现错误后,会立即重新分析以前的前瞻令牌。如果
这是不可接受的,那么宏可能会用于清除
这个令牌。在错误规则的
行动。
请参阅在操作中使用的特殊功能。yyclearin
yyclearin;
例如,假设在语法错误上,错误处理例程为 调用,将输入流推进到解析应该的某个点 再次开始。词法扫描程序返回的下一个符号是 可能是正确的。之前的 lookahead 令牌应该被丢弃 用 ''.yyclearin;
当解析器生成 1 时,表达式生成 1
正在从语法错误中恢复,否则为 0。
从语法中恢复时,语法错误诊断将被禁止显示
错误。YYRECOVERING ()
Bison 范式是先解析代币,然后将它们分组为更大的代币 句法单位。在许多语言中,令牌的含义受以下因素的影响 它的背景。尽管这违反了野牛范式,但某些技术 (称为 kludges)可能使您能够为此类编写 Bison 解析器 语言。
(实际上,“kludge”是指任何完成其工作但 既不干净也不坚固。
C 语言具有上下文依赖性:标识符的使用方式 取决于它现在的含义是什么。例如,请考虑以下情况:
foo (x);
这看起来像一个函数调用语句,但 if 是一个 typedef
name,那么这其实就是一个声明。野牛怎么能
解析器 C 决定如何解析此输入?foo
x
GNU C 中使用的方法是具有两种不同的标记类型,以及 .当发现
identifier,它按顺序查找标识符的当前声明
确定要返回的令牌类型:如果标识符为
声明为 typedef,否则。IDENTIFIER
TYPENAME
yylex
TYPENAME
IDENTIFIER
然后,语法规则可以通过选择以下来表示上下文依赖性
要识别的令牌类型。 被接受为表达式,
但事实并非如此。 可以启动声明,但不能。在标识符含义的上下文中
并不重要,例如在可以掩盖
typedef 名称,要么就是
已接受 - 两种令牌类型中的每一种都有一个规则。IDENTIFIER
TYPENAME
TYPENAME
IDENTIFIER
TYPENAME
IDENTIFIER
这种技术很容易使用,如果决定哪种 允许的标识符是在靠近标识符所在位置的位置创建的 解析。但在 C 中,情况并非总是如此:C 允许声明 如果指定了显式类型,请重新声明 typedef 名称 早些时候:
typedef int foo, bar; int baz (void)
{ static bar (bar); /* redeclarebar
as static variable */ extern foo foo (foo); /* redeclarefoo
as function */ return foo (bar); }
不幸的是,被声明的名称与声明是分开的 通过复杂的句法结构——“声明器”来构建自己。
因此,需要复制 C 的 Bison 解析器的一部分,使用 所有非终端名称都已更改:一次用于解析 可以重新定义 typedef 名称,并且一次用于解析 不能这样做的声明。这是 重复,为简洁起见省略了操作:
initdcl: declarator maybeasm '=' init | declarator maybeasm ;
notype_initdcl: notype_declarator maybeasm '=' init | notype_declarator maybeasm ;
这里可以重新声明 typedef 名称,但不能。和之间的区别是一回事。initdcl
notype_initdcl
declarator
notype_declarator
这种技术与词汇搭配之间有一些相似之处 (如下所述),在改变词汇分析的信息中是 在程序的其他部分解析期间更改。区别在于 这里的信息是全局的,并用于其他目的 程序。真正的词汇搭配有一个特殊用途的标志,由 句法上下文。
下一篇: 词法绑定与错误恢复, 上一篇: 标记种类中的语义信息, 上一篇: 处理上下文依赖 [内容][索引]
处理上下文依赖性的一种方法是词汇连接:标志 这是由 Bison 动作设置的,其目的是改变代币的方式 解析。
例如,假设我们有一种语言,模糊地像 C 语言,但有一个特殊的
构造 ''。关键字出现后
括号中所有整数均为十六进制的表达式。在
特别是,标记 '' 必须被视为整数,而不是
作为标识符(如果它出现在该上下文中)。这是你如何做到的:hex (hex-expr)hex
a1b
%{ int hexflag; int yylex (void); void yyerror (char const *); %} %% …
expr: IDENTIFIER | constant | HEX '(' { hexflag = 1; } expr ')' { hexflag = 0; $$ = $4; } | expr '+' expr { $$ = make_sum ($1, $3); } … ;
constant: INTEGER | STRING ;
在这里,我们假设 查看 的值 ;什么时候
它是非零的,所有整数都以十六进制解析,并且标记开始
如果可能,将字母解析为整数。yylex
hexflag
语法文件序言中显示的声明
需要使操作可访问它(参见序言)。你必须
还要写代码以服从标志。hexflag
yylex
词法绑定对您拥有的任何错误恢复规则都有严格的要求。 请参阅错误恢复。
这样做的原因是错误恢复规则的目的是 中止一个构造的解析,并在某个更大的构造中恢复。 例如,在类 C 语言中,典型的错误恢复规则是跳过 标记,直到下一个分号,然后开始一个新的语句,如下所示:
stmt: expr ';' | IF '(' expr ')' stmt { … } … | error ';' { hexflag = 0; } ;
如果“”中间有语法错误
构造,则此错误规则将适用,然后操作
已完成的 '' 将永远不会运行。所以会
在输入的整个其余部分保持设置,或直到下一个关键字为止,导致标识符被误解为整数。hex (expr)hex (expr)hexflag
hex
为避免此问题,错误恢复规则本身会清除 。hexflag
可能还存在在表达式中起作用的错误恢复规则。 例如,括号内可能有一条规则适用 并跳到右括号:
expr: … | '(' expr ')' { $$ = $2; } | '(' error ')' …
如果此规则在构造中起作用,则它不会中止
该结构(因为它适用于括号的内部级别
构造)。因此,它不应该清除标志:其余的
应使用仍然有效的标志来解析构造。hex
hex
如果存在错误恢复规则,该规则可能会中止或可能不会中止,具体取决于具体情况,该怎么办?没有
编写操作以确定构造是否
是否中止。因此,如果您使用词汇搭配,则最好
请确保您的错误恢复规则不属于此类规则。每个规则必须
这样你就可以确定它总是会,或者总是不会,必须这样做
清除标志。hex
hex
开发解析器可能是一个挑战,尤其是在您不了解的情况下 算法(参见 Bison 解析器算法)。本章解释如何理解 并调试解析器。
用户面临的最常见问题是解决他们的冲突。要修复它们, 第一步是了解它们在给定语法中是如何出现的。这是 通过自动生成反例变得更加容易,覆盖在 第一部分(参见反例的生成)。
不过,在大多数情况下,观察自动机的结构仍然是 需要。以下各节介绍如何生成和读取 自动机的详细结构描述。有几种格式 可用:
最后一部分重点介绍解析器的动态部分:如何启用 并了解解析器运行时跟踪(请参阅跟踪解析器)。
解决冲突可能是LR设计中最微妙的部分 解析器,如本文中专门介绍它们的章节数量所示 非常文档。要解决冲突,必须了解它:什么时候 它发生吗?是因为语法上的缺陷吗?是因为 LR(1) 无法应付这种语法?
一个困难是冲突发生在自动机中,它可以 将它们与语法本身的问题联系起来是很棘手的。跟 经验和耐心,分析详细描述的 automaton(参见 Understanding Your Parser)允许人们找到示例字符串 达到这些冲突。
由于反例的生成,这项任务变得更加容易, 最初由 Chinawat Isradisaikul 和 Andrew Myers 开发 (见 Isradisaikul 2015)。
作为第一个示例,请参阅 Shift/Reduce Conflicts 的语法,其特点是 一个班次/减少冲突:
$ bison else.y else.y: warning: 1 shift/reduce conflict [-Wconflicts-sr] else.y: note: rerun with option '-Wcounterexamples' to generate conflict counterexamples
让我们使用选项 / 重新运行:bison
-Wcex-Wcounterexamples
else.y: warning: 1 shift/reduce conflict [-Wconflicts-sr] else.y: warning: shift/reduce conflict on token "else" [-Wcounterexamples]
Example: "if" expr "then" "if" expr "then" stmt • "else" stmt Shift derivation if_stmt ↳ 3: "if" expr "then" stmt ↳ 2: if_stmt ↳ 4: "if" expr "then" stmt • "else" stmt Example: "if" expr "then" "if" expr "then" stmt • "else" stmt Reduce derivation if_stmt ↳ 4: "if" expr "then" stmt "else" stmt ↳ 2: if_stmt ↳ 3: "if" expr "then" stmt •
这显示了一个表达式的两种不同推导,这证明了 语法模棱两可。
举一个更微妙的例子,考虑 Reduce/Reduce 冲突的示例语法,它具有 reduce/reduce 冲突:
%% sequence: %empty | maybeword | sequence "word" ; maybeword: %empty | "word" ;
Bison 生成以下反例:
$ bison -Wcex sequence.y sequence.y: warning: 1 shift/reduce conflict [-Wconflicts-sr] sequence.y: warning: 2 reduce/reduce conflicts [-Wconflicts-rr]
sequence.y: warning: shift/reduce conflict on token "word" [-Wcounterexamples] Example: • "word" Shift derivation sequence ↳ 2: maybeword ↳ 5: • "word" Example: • "word" Reduce derivation sequence ↳ 3: sequence "word" ↳ 1: •
sequence.y: warning: reduce/reduce conflict on tokens $end, "word" [-Wcounterexamples] Example: • First reduce derivation sequence ↳ 1: • Example: • Second reduce derivation sequence ↳ 2: maybeword ↳ 4: •
sequence.y: warning: shift/reduce conflict on token "word" [-Wcounterexamples] Example: • "word" Shift derivation sequence ↳ 2: maybeword ↳ 5: • "word" Example: • "word" Reduce derivation sequence ↳ 3: sequence "word" ↳ 2: maybeword ↳ 4: •
sequence.y:8.3-45: warning: rule useless in parser due to conflicts [-Wother] 8 | %empty { printf ("empty maybeword\n"); } | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
这三个冲突中的每一个都再次证明了语法是模棱两可的。 例如,第二个冲突(reduce/reduce 冲突)表明 语法以两种不同的方式接受空输入。
有时,搜索不会找到可以分为两个的示例 方式。在这些情况下,反例生成将提供两个示例 直到点都是一样的。最值得注意的是,这将发生在以下情况下 你的语法需要一个更强的解析器(更多的前瞻性,LR 而不是 拉尔)。下面的例子不是 LR(1):
%token ID %% s: a ID a: expr expr: %empty | expr ID ','
bison
报告:
ids.y: warning: 1 shift/reduce conflict [-Wconflicts-sr] ids.y: warning: shift/reduce conflict on token ID [-Wcounterexamples]
First example: expr • ID ',' ID $end Shift derivation $accept ↳ 0: s $end ↳ 1: a ID ↳ 2: expr ↳ 4: expr • ID ',' Second example: expr • ID $end Reduce derivation $accept ↳ 0: s $end ↳ 1: a ID ↳ 2: expr •
ids.y:4.4-7: warning: rule useless in parser due to conflicts [-Wother] 4 | a: expr | ^~~~
此冲突是由于分析器没有足够的信息来了解而引起的
这两个例子之间的区别。解析器需要一个
额外的 Lookahead 标记,用于了解逗号是否跟在 after .这些类型的冲突往往更多
很难修复,通常需要对语法进行返工。在这种情况下,
它可以通过围绕递归进行更改来修复:。ID
expr
expr: ID | ',' expr
ID
或者,您可能还需要考虑使用 GLR 解析器 (请参阅编写 GLR 解析器)。
有时,原位查看反例很有用:与 自动机报告(请参阅了解解析器,特别是状态 8)。
Bison 解析器是 shift/reduce 自动机(参见 Bison 解析器算法)。在一些 案例(比人们希望的要频繁得多),看看这个自动机是 需要调整或简单地修复解析器。
文本文件是在指定选项或时生成的,请参阅调用 Bison。它的名字是由 从解析器实现文件中删除“”或“” 名称,并改为添加“”。因此,如果语法文件是 ,则默认调用解析器实现文件。因此,详细的输出文件称为 。--report--verbose.tab.c.c.outputfoo.yfoo.tab.cfoo.output
以下语法文件 ,将在续集中使用:calc.y
%union { int ival; const char *sval; }
%token <ival> NUM %nterm <ival> exp
%token <sval> STR %nterm <sval> useless
%left '+' '-' %left '*'
%%
exp: exp '+' exp | exp '-' exp | exp '*' exp | exp '/' exp | NUM ;
useless: STR; %%
bison
报告:
calc.y: warning: 1 nonterminal useless in grammar [-Wother] calc.y: warning: 1 rule useless in grammar [-Wother] calc.y:19.1-7: warning: nonterminal useless in grammar: useless [-Wother] 19 | useless: STR; | ^~~~~~~ calc.y: warning: 7 shift/reduce conflicts [-Wconflicts-sr] calc.y: note: rerun with option '-Wcounterexamples' to generate conflict counterexamples
回到 calc 示例,当给定时, 除了 之外,它还创建一个文件,其内容详述如下。输出的顺序和精确的顺序 表述可能会有所不同,但解释是相同的。--report=statecalc.tab.ccalc.output
第一部分报告无用的令牌、非终端和规则。无用 为了生成更小的解析器,删除了非终端和规则,但 无用的令牌会被保留,因为它们可能会被扫描程序使用(注意 下面“无用”和“未使用”之间的区别):
Nonterminals useless in grammar useless Terminals unused in grammar STR Rules useless in grammar 6 useless: STR
下一节将列出仍然存在冲突的状态。
State 8 conflicts: 1 shift/reduce State 9 conflicts: 1 shift/reduce State 10 conflicts: 1 shift/reduce State 11 conflicts: 4 shift/reduce
然后 Bison 重现了它使用的确切语法:
Grammar 0 $accept: exp $end 1 exp: exp '+' exp 2 | exp '-' exp 3 | exp '*' exp 4 | exp '/' exp 5 | NUM
并报告符号的用法:
Terminals, with rules where they appear $end (0) 0 '*' (42) 3 '+' (43) 1 '-' (45) 2 '/' (47) 4 error (256) NUM <ival> (258) 5 STR <sval> (259)
Nonterminals, with rules where they appear $accept (9) on left: 0 exp <ival> (10) on left: 1 2 3 4 5 on right: 0 1 2 3 4
然后,野牛继续进入自动机本身,用 它的项集,也称为虚线规则。每个项目都是一个 生产规则以及标记位置的点 ('') 输入光标。.
State 0 0 $accept: • exp $end NUM shift, and go to state 1 exp go to state 2
其内容如下:“状态 0 对应于处于非常
解析的开始,在初始规则中,就在开始之前
符号(这里,)。当解析器返回到此状态时,权限
在简化生成 的规则后,控件
流跳转到状态 2。如果非终端上没有这样的转换
符号,并且前瞻是 ,则此标记被转移到
解析堆栈和控制流跳转到状态 1。任何其他
Lookahead 会触发语法错误。exp
exp
NUM
尽管状态 0 中唯一的活动规则似乎是规则 0,但
报告列为展望令牌,因为可以
在任何派生 . 的规则的开头默认情况下,Bison
报告项集的所谓核心或内核,但如果
您还希望查看更多详细信息,以便调用以列出派生项:NUM
NUM
exp
bison
--report=itemset
State 0 0 $accept: • exp $end 1 exp: • exp '+' exp 2 | • exp '-' exp 3 | • exp '*' exp 4 | • exp '/' exp 5 | • NUM NUM shift, and go to state 1 exp go to state 2
在该州 1...
State 1 5 exp: NUM • $default reduce using rule 5 (exp)
规则 5 “”已完成。无论前瞻令牌是什么 (''),解析器将减少它。如果它来自国家 0,那么,在此减少之后,它将返回到状态 0,并将跳转到 状态 2 ('')。exp: NUM;$defaultexp: go to state 2
State 2 0 $accept: exp • $end 1 exp: exp • '+' exp 2 | exp • '-' exp 3 | exp • '*' exp 4 | exp • '/' exp $end shift, and go to state 3 '+' shift, and go to state 4 '-' shift, and go to state 5 '*' shift, and go to state 6 '/' shift, and go to state 7
在状态 2 中,自动机只能移动一个符号。例如,因为 项目 '',如果前瞻是 '',则为 移到解析堆栈上,自动机跳转到状态 4, 对应于项目 ''。由于没有 默认操作,任何未列出的展望都会触发语法错误。exp: exp • '+' exp+exp: exp '+' • exp
状态 3 被命名为最终状态,或接受状态 状态:
State 3 0 $accept: exp $end • $default accept
初始规则完成(开始符号和输入结束符为 read),解析成功退出。
状态 4 到 7 的解释很简单,留给 读者。
State 4 1 exp: exp '+' • exp NUM shift, and go to state 1 exp go to state 8 State 5 2 exp: exp '-' • exp NUM shift, and go to state 1 exp go to state 9 State 6 3 exp: exp '*' • exp NUM shift, and go to state 1 exp go to state 10 State 7 4 exp: exp '/' • exp NUM shift, and go to state 1 exp go to state 11
正如报告开头所宣布的那样,“:State 8 conflicts: 1 shift/reduce
State 8 1 exp: exp • '+' exp 1 | exp '+' exp • 2 | exp • '-' exp 3 | exp • '*' exp 4 | exp • '/' exp '*' shift, and go to state 6 '/' shift, and go to state 7 '/' [reduce using rule 1 (exp)] $default reduce using rule 1 (exp)
事实上,有两个操作与前瞻 '' 相关联: 要么移动(并转到状态 7),要么减少规则 1。这 冲突意味着语法不明确,或者解析器缺乏 做出正确决定的信息。事实上,语法是 模棱两可,因为,由于我们没有指定 '' 的优先级,因此 句子 '' 可以解析为 '',对应于移动 '',也可以解析为 '',对应于减少规则 1。//NUM + NUM / NUMNUM + (NUM / NUM)/(NUM + NUM) / NUM
因为在确定性解析中,可以做出一个单一的决定,Bison 任意选择禁用缩减,请参阅 Shift/Reduce 冲突。 放弃的操作在方括号之间报告。
请注意,之前的所有状态都有一个可能的操作:要么 移动下一个令牌并转到相应的状态,或者 减少单个规则。在其他情况下,即当可以移位和减少时,或者当多次减少时 可能,需要提前查看才能选择操作。状态 8 是 一种这样的状态:如果前瞻是“”或“”,则操作 正在移动,否则操作将减少规则 1。换言之, 与规则 1 相对应的前两项不符合条件,如果 Lookahead token 是 '',因为我们指定 '' 具有更高的 优先于“”。更一般地说,某些项目仅符合条件 具有一些可能的 Lookahead 令牌。当使用 时,Bison 指定以下 lookahead 令牌:*/**+--report=lookahead
State 8 1 exp: exp • '+' exp 1 | exp '+' exp • [$end, '+', '-', '/'] 2 | exp • '-' exp 3 | exp • '*' exp 4 | exp • '/' exp '*' shift, and go to state 6 '/' shift, and go to state 7 '/' [reduce using rule 1 (exp)] $default reduce using rule 1 (exp)
但请注意,虽然 '' 是不明确的(这会导致 '')、'' 上的冲突不是:冲突是 由于关联性和优先指令而得到解决。如果使用 调用,则 Bison 包含有关已求解的信息 报告中的冲突:NUM + NUM / NUM/NUM + NUM * NUM--report=solved
Conflict between rule 1 and token '+' resolved as reduce (%left '+'). Conflict between rule 1 and token '-' resolved as reduce (%left '-'). Conflict between rule 1 and token '*' resolved as shift ('+' < '*').
当给定时,将生成
报告中的反例,并增加了相应的项目
(参见反例的生成)。--report=counterexamplesbison
shift/reduce conflict on token '/': 1 exp: exp '+' exp • 4 exp: exp • '/' exp
Example: exp '+' exp • '/' exp Shift derivation exp ↳ 1: exp '+' exp ↳ 4: exp • '/' exp Example: exp '+' exp • '/' exp Reduce derivation exp ↳ 4: exp '/' exp ↳ 1: exp '+' exp •
这显示了同一语法中的两个独立派生:
‘’.派生显示了您的规则将如何解析
举个例子。在这里,第一个推导在看到时完成约简
'',导致 '' 被分组为 .第二个
导数在 '' 上移动,导致 '' 被分组为
一。因此,很容易看出,添加
优先级/关联性指令将解决此冲突。exp
e1 + e2 / e3/e1 + e2exp
/e2 / e3exp
其余状态类似:
State 9 1 exp: exp • '+' exp 2 | exp • '-' exp 2 | exp '-' exp • 3 | exp • '*' exp 4 | exp • '/' exp '*' shift, and go to state 6 '/' shift, and go to state 7 '/' [reduce using rule 2 (exp)] $default reduce using rule 2 (exp)
State 10 1 exp: exp • '+' exp 2 | exp • '-' exp 3 | exp • '*' exp 3 | exp '*' exp • 4 | exp • '/' exp '/' shift, and go to state 7 '/' [reduce using rule 3 (exp)] $default reduce using rule 3 (exp)
State 11 1 exp: exp • '+' exp 2 | exp • '-' exp 3 | exp • '*' exp 4 | exp • '/' exp 4 | exp '/' exp • '+' shift, and go to state 4 '-' shift, and go to state 5 '*' shift, and go to state 6 '/' shift, and go to state 7 '+' [reduce using rule 4 (exp)] '-' [reduce using rule 4 (exp)] '*' [reduce using rule 4 (exp)] '/' [reduce using rule 4 (exp)] $default reduce using rule 4 (exp)
请注意,状态 11 包含冲突,这不仅是因为缺少 “”相对于“”、“”和“”的优先级,但 也因为未指定“”的关联性。/+-*/
Bison 还可以通过 XML 文件生成此输出的 HTML 版本,并且 XSLT 处理(请参阅以多种格式可视化分析器)。
下一篇: 以多种格式可视化解析器, 上一篇: 了解解析器, 上一篇: 调试解析器 [内容][索引]
作为更好地了解移位/减少的另一种方法 自动机对应 Bison 解析器,可以生成一个 DOT 文件。注意 用这个调试真正的语法充其量是乏味的,而且不切实际 大多数时候,因为生成的文件很大(生成 其中的 PDF 或 PNG 文件将花费很长时间,而且通常情况下会 由于内存耗尽而失败)。此选项是为初学者设计的, 以帮助他们理解 LR 解析器。
指定选项时生成此文件 (请参阅调用 Bison)。它的名字是通过删除 '' 或 '' 从解析器实现文件名中获取,以及 改为添加“”。如果语法文件是 , Graphviz 输出文件称为 。DOT文件也可以是 通过 XML 文件和 XSLT 处理生成(请参阅以多种格式可视化分析器)。--graph.tab.c.c.gvfoo.yfoo.gv
以下语法文件 ,将在续集中使用:rr.y
%%
exp: a ";" | b "."; a: "0"; b: "0";
图形输出 (见图 8.1) 与文本非常相似,因此更容易理解 对它们进行直接比较。请参阅调试分析器,了解详细信息 对文本报告的分析。
每个状态的项目(虚线规则)在图形节点中组合在一起。 它们的编号与详细文件中的编号相同。请参阅以下内容 点,关于过渡,例如
当使用 调用时,前瞻令牌,当 需要,在方括号之间的相关规则旁边显示为 逗号分隔列表。图中表示的情况就是这种情况 减少,如下。--report=lookaheads
转换表示为电流和 目标状态。
移位显示为实心箭头,标有 lookahead 标记 转变。下面介绍了文件中的缩减:rr.output
State 3 1 exp: a • ";" ";" shift, and go to state 6
图形的这一部分的 Graphviz 渲染可以是:
减少显示为实心箭头,指向菱形节点 承载减少规则的编号。箭头标有 适当的逗号分隔的 Lookahead 令牌。如果减少是默认值 对于给定状态的操作,没有这样的标签。
这是在详细文件中表示减少的方式:rr.output
State 1 3 a: "0" • [";"] 4 b: "0" • ["."] "." reduce using rule 4 (b) $default reduce using rule 3 (a)
图形的这一部分的 Graphviz 渲染可以是:
当存在未解决的冲突时,因为在确定性解析中 可以做出一个决定,Bison可以任意选择禁用一个 reduction,请参阅 Shift/Reduce 冲突。放弃的操作 在这些节点上以红色填充颜色来区分,就像它们一样 在详细文件中的方括号之间报告。
与规则编号 0 相对应的减少是接受 州。它显示为一颗蓝色钻石,标有“Acc”。
'' 跳跃过渡表示为虚线方位 要跳转到的规则的名称。go to
Bison 支持两种主要的报告格式:文本输出
(请参阅了解解析器) 调用时
带选项和 DOT
(请参阅可视化解析器) 当调用时
选择。然而
另一种替代方法是输出一个 XML 文件,然后可以将 呈现为相当于
详细文件,或作为同一文件的 HTML 版本,可单击
过渡,甚至作为 DOT。和 DOT 文件通过以下方式获取
XSLT 与通过调用 options 或 .--verbose--graphxsltproc
.outputbison
--verbose--graph
XML 文件是在指定选项或时生成的,请参阅调用 Bison。 如果未指定,则通过删除“”或“”来命名 从解析器实现文件名中,然后添加“”。 例如,如果语法文件是 ,则默认 XML 输出 文件是 。-x--xml[=FILE].tab.c.c.xmlfoo.yfoo.xml
Bison 附带一个目录,其中包含 XSL Transformation 要应用于 XML 文件的文件。它们的名称是明确的:data/xslt
用于输出自动机的 DOT 可视化的副本。
用于输出“”文件的副本。.output
用于输出 '' 文件的 xhtml 增强功能。.output
示例用法(需要):xsltproc
$ bison -x gr.y
$ bison --print-datadir /usr/local/share/bison
$ xsltproc /usr/local/share/bison/xslt/xml2xhtml.xsl gr.xml >gr.html
上一篇: 以多种格式可视化解析器, 上一篇: 调试解析器 [内容][索引]
当 Bison 语法编译正确但解析“不正确”时,解析器跟踪功能有助于找出原因。yydebug
Next: 启用 mfcalc
的调试跟踪,向上: 跟踪解析器 [内容][索引]
有几种方法可以编译跟踪设施,在 优先顺序递减:
添加 '' 指令(请参阅 %define Summary),或传递选项 (请参阅调整解析器)。这是 Bison 扩展。除非 POSIX 和 Yacc 的便携性对您很重要,这是首选解决方案。%define parse.trace-Dparse.trace
运行 Bison 时使用该选项(请参阅调用 Bison)。跟
'',它定义为 1,否则它
定义为 1。-t%define api.prefix {c}CDEBUG
YYDEBUG
添加指令(请参阅 Bison 声明摘要)。这头野牛
维护扩展以实现向后兼容性;请改用。%debug
%define
parse.trace
YYDEBUG
(仅限 C/C++)¶将宏定义为非零值时编译
解析 器。这符合 POSIX Yacc 标准。您可以用作编译器选项,也可以将 '' 放在语法文件的序言中(参见序言)。YYDEBUG
-DYYDEBUG=1#define
YYDEBUG 1
如果使用变量(请参阅同一程序中的多个解析器),例如 '',则如果定义了变量,则其值控制
跟踪功能(当且仅当非零时启用);否则跟踪是
当且仅当为非零时启用。%define
api.prefix
%define
api.prefix {c}CDEBUG
YYDEBUG
在 C++ 中,POSIX 合规性没有意义,请避免此选项,而首选
‘’.如果你的宏
在错误的地方(例如,在 '' 而不是 ''),解析器类将有两个不同的定义,因此
导致 ODR 违规和愉快的调试时间。%define parse.trace#define
YYDEBUG
%code top%code
require
我们建议您始终启用 trace 选项,以便调试 总是可能的。
在 C 语言中,跟踪工具输出带有宏调用的消息,其形式为 where 和 是常用格式和可变参数。如果
定义为非零值但不定义 ,将自动包含并定义为 。YYFPRINTF (stderr, format, args)
formatargsprintf
YYDEBUG
YYFPRINTF
<stdio.h>
YYFPRINTF
fprintf
一旦你编译了带有跟踪工具的程序,请求的方式
跟踪是在变量中存储非零值。您可以
通过让 C 代码来做到这一点(也许),或者你可以
使用 C 调试器更改值。yydebug
main
解析器在非零时执行的每个步骤都会生成一行
或两个跟踪信息,写在.跟踪消息
告诉你这些事情:yydebug
stderr
yylex
为了理解这些信息,参考自动机会有所帮助 description 文件(请参阅了解解析器)。这 文件显示了每个状态在各种规则中的位置的含义, 以及每个状态将如何处理每个可能的输入令牌。作为你 阅读连续的跟踪消息,可以看到解析器是 根据其在列表文件中的规范运行。最终 你会到达发生不良事情的地方,而你 将看到语法的哪些部分是罪魁祸首。
解析器实现文件是一个 C/C++/D/Java 程序,您可以使用 调试器,但要解释它在做什么并不容易。这 Parser 函数是一个有限状态机解释器,除了 操作:它一遍又一遍地执行相同的代码。只有 变量显示它在语法中的位置。
mfcalc
调试信息通常给出读取的每个令牌的令牌类型,
但不是它的语义值。该指令允许指定
如何报告语义值,请参阅打印语义值。%printer
作为演示,考虑多功能
计算器,(参见多功能计算器:mfcalc
)。启用运行时
跟踪和语义值报告,在其
序幕:%printer
mfcalc
/* Generate the parser description file. */ %verbose /* Enable run-time traces (yydebug). */ %define parse.trace /* Formatting semantic values. */ %printer { fprintf (yyo, "%s", $$->name); } VAR; %printer { fprintf (yyo, "%s()", $$->name); } FUN; %printer { fprintf (yyo, "%g", $$); } <double>;
该指令指示 Bison 生成运行时跟踪
支持。然后,这些跟踪的激活在运行时由变量控制,默认情况下该变量处于禁用状态。因为这些痕迹
将参考解析器的“状态”,询问
创建该解析器的描述;这就是(诚然
Ill-named) 指令。%define
yydebug
%verbose
这组指令演示了如何格式化
跟踪中的语义值。请注意,可以完成规范
在符号类型(例如,或)或类型上
标签: 因为 是 和 的类型 ,
这台打印机将用于他们。%printer
VAR
FUN
<double>
NUM
exp
下面是运行时跟踪提供的信息示例。痕迹 被发送到标准错误。
$ echo 'sin(1-1)' | ./mfcalc -p Starting parse Entering state 0 Reducing stack by rule 1 (line 34): -> $$ = nterm input () Stack now 0 Entering state 1
第一批显示了此语法的特定功能:第一条规则
(在第 34 行中,甚至可以在没有
查找第一个令牌。得到的左侧符号 () 是
无值 ('') 非终端 ()。mfcalc.y$$
()input
nterm
然后,解析器调用扫描程序。
Reading a token Next token is token FUN (sin()) Shifting token FUN (sin()) Entering state 6
该标记 () 是一个函数 (),其值为
'',按照我们的规范格式化:''。
解析器存储 () 该令牌和其他令牌,直到它可以执行
关于它的东西。token
FUN
sin%printer
sin()Shifting
Reading a token Next token is token '(' () Shifting token '(' () Entering state 14 Reading a token Next token is token NUM (1.000000) Shifting token NUM (1.000000) Entering state 4 Reducing stack by rule 6 (line 44): $1 = token NUM (1.000000) -> $$ = nterm exp (1.000000) Stack now 0 1 6 14 Entering state 24
前面的简化演示了以下指令:令牌和生成的非终端都具有 '' 作为值。%printer
<double>
NUM
exp
1
Reading a token Next token is token '-' () Shifting token '-' () Entering state 17 Reading a token Next token is token NUM (1.000000) Shifting token NUM (1.000000) Entering state 4 Reducing stack by rule 6 (line 44): $1 = token NUM (1.000000) -> $$ = nterm exp (1.000000) Stack now 0 1 6 14 24 17 Entering state 26 Reading a token Next token is token ')' () Reducing stack by rule 11 (line 49): $1 = nterm exp (1.000000) $2 = token '-' () $3 = nterm exp (1.000000) -> $$ = nterm exp (0.000000) Stack now 0 1 6 14 Entering state 24
减法的规则只是减少了。解析器即将
发现对 的调用结束。sin
Next token is token ')' () Shifting token ')' () Entering state 31 Reducing stack by rule 9 (line 47): $1 = token FUN (sin()) $2 = token '(' () $3 = nterm exp (0.000000) $4 = token ')' () -> $$ = nterm exp (0.000000) Stack now 0 1 Entering state 11
最后,行尾允许解析器完成计算,并且 显示其结果。
Reading a token Next token is token '\n' () Shifting token '\n' () Entering state 22 Reducing stack by rule 4 (line 40): $1 = nterm exp (0.000000) $2 = token '\n' () ⇒ 0 -> $$ = nterm line () Stack now 0 1 Entering state 10 Reducing stack by rule 2 (line 35): $1 = nterm input () $2 = nterm line () -> $$ = nterm input () Stack now 0 Entering state 1
解析器已返回到状态 1,在该状态中,它正在等待下一个 表达式的计算方法,或者用于文件末尾令牌,这会导致 完成解析。
Reading a token Now at end of input. Shifting token $end () Entering state 2 Stack now 0 1 2 Cleanup: popping token $end () Cleanup: popping nterm input ()
下一篇: 用其他语言编写的解析器, 上一篇: 调试你的解析器, 上一篇: Bison [Contents][Index]
调用 Bison 的常用方法如下:
$ bison file
这是语法文件名,通常以“”结尾。 解析器实现文件的名称是通过替换“' 替换为 '' 并删除任何前导目录。因此, '' 文件名生成 ,'' 文件名生成 。以防万一 您正在语法文件中编写 C++ 代码而不是 C,以命名它或 .然后,输出文件将采用 扩展,如给定的扩展作为输入(分别为 和 )。此功能对以下所有选项都有效 操作文件名,如 或 .file.y.y.tab.cbison foo.yfoo.tab.cbison hack/foo.yfoo.tab.cfoo.yppfoo.y++foo.tab.cppfoo.tab.c++-o-d
例如:
$ bison -d file.yxx
将产生 和 、 和file.tab.cxxfile.tab.hxx
$ bison -d -o output.c++ file.y
将产生 和 .output.c++output.h++
为了与 POSIX 兼容,标准 Bison 发行版还包含
一个名为 shell 脚本,该脚本使用选项调用 Bison。yacc
-y
退出状态为:bison
当没有错误时。警告,这是对可疑的诊断 构造,不要更改退出状态,除非它们被变成 错误(请参阅 -Werror)。
当出现错误时。未生成任何文件(生成的报告除外) by 等)。特别是,输出文件可能 存在没有改变。--verbose
当不满足语法的版本要求时
文件。请参阅需要 Bison 版本。未生成或更改任何文件。bison
Bison 支持传统的单字母选项和助记符长 选项名称。长选项名称用 而不是 表示。允许使用期权名称的缩写,只要它们 是独一无二的。当长选项采用参数时,例如 ,将选项名称和参数连接起来 ‘’.-----file-prefix=
以下是可用于 Bison 的选项列表。它后面跟着一个 按长选项字母顺序排列的十字键。
控制 的全局行为的选项。bison
将命令行选项的摘要打印到 Bison 并退出。
打印 Bison 的版本号并退出。
打印包含与区域设置相关的数据的目录的名称。
打印包含框架、CSS 和 XSLT 的目录的名称。
更新语法文件(删除重复项、更新已弃用的指令、
等)并退出(即不生成任何输出文件)。留下一个
备份原始文件,并附加一个。例如:~
$ cat foo.y %error-verbose %define parse.error verbose %% exp:;
$ bison -u foo.y foo.y:1.1-14: warning: deprecated directive, use '%define parse.error verbose' [-Wdeprecated] 1 | %error-verbose | ^~~~~~~~~~~~~~ foo.y:2.1-27: warning: %define variable 'parse.error' redefined [-Wother] 2 | %define parse.error verbose | ^~~~~~~~~~~~~~~~~~~~~~~~~~~ foo.y:1.1-14: previous definition 1 | %error-verbose | ^~~~~~~~~~~~~~ bison: file 'foo.y' was updated (backup: 'foo.y~')
$ cat foo.y %define parse.error verbose %% exp:;
有关详细信息,请参阅下面的文档。--feature=fixit
激活杂项。 可以是以下之一:featureFeature
caret
diagnostics-show-caret
以类似于 GCC 或 Clang 的方式显示插入符号错误。使用消息提供的位置 引用源文件的相应行,在 它的重要部分与插入符号 ('')。下面是一个示例,使用 以下文件:-fdiagnostics-show-caret-fcaret-diagnostics^in.y
%nterm <ival> exp %% exp: exp '+' exp { $exp = $1 + $2; };
当调用(或不调用)时,Bison 将报告:-fcaret
in.y:3.20-23: error: ambiguous reference: '$exp' 3 | exp: exp '+' exp { $exp = $1 + $2; }; | ^~~~
in.y:3.1-3: refers to: $exp at $$ 3 | exp: exp '+' exp { $exp = $1 + $2; }; | ^~~
in.y:3.6-8: refers to: $exp at $1 3 | exp: exp '+' exp { $exp = $1 + $2; }; | ^~~
in.y:3.14-16: refers to: $exp at $3 3 | exp: exp '+' exp { $exp = $1 + $2; }; | ^~~
in.y:3.32-33: error: $2 of 'exp' has no declared type 3 | exp: exp '+' exp { $exp = $1 + $2; }; | ^~
然而,当使用 调用时,Bison 只会报告:-fno-caret
in.y:3.20-23: error: ambiguous reference: '$exp' in.y:3.1-3: refers to: $exp at $$ in.y:3.6-8: refers to: $exp at $1 in.y:3.14-16: refers to: $exp at $3 in.y:3.32-33: error: $2 of 'exp' has no declared type
默认情况下,此选项处于激活状态。
fixit
diagnostics-parseable-fixits
以类似于 GCC 和 Clang 的方式显示机器可读的修复程序。-fdiagnostics-parseable-fixits
为重复指令生成修复程序:
$ cat foo.y %define api.prefix {foo} %define api.prefix {bar} %% exp:;
$ bison -ffixit foo.y foo.y:2.1-24: error: %define variable 'api.prefix' redefined 2 | %define api.prefix {bar} | ^~~~~~~~~~~~~~~~~~~~~~~~ foo.y:1.1-24: previous definition 1 | %define api.prefix {foo} | ^~~~~~~~~~~~~~~~~~~~~~~~ fix-it:"foo.y":{2:1-2:25}:"" foo.y: warning: fix-its can be applied. Rerun with option '--update'. [-Wother]
它们也是为了更新已弃用的指令而生成的,除非给出:-Wno-deprecated
$ cat /tmp/foo.yy %error-verbose %name-prefix "foo" %% exp:;
$ bison foo.y foo.y:1.1-14: warning: deprecated directive, use '%define parse.error verbose' [-Wdeprecated] 1 | %error-verbose | ^~~~~~~~~~~~~~ foo.y:2.1-18: warning: deprecated directive, use '%define api.prefix {foo}' [-Wdeprecated] 2 | %name-prefix "foo" | ^~~~~~~~~~~~~~~~~~ foo.y: warning: fix-its can be applied. Rerun with option '--update'. [-Wother]
当给定选项 / 时,fix-its 将自行应用。请参阅上面的文档。bison
-u--update
syntax-only
不要生成输出文件。这个功能的名称有点 误导,因为不仅仅是检查语法:每个阶段都在运行 (包括检查冲突),除了生成 输出文件。
控制诊断的选项。
-W [category]
--warnings[=category]
输出警告落在 . 可以是一个 之:categorycategory
conflicts-sr
conflicts-rr
S/R 和 R/R 冲突。默认情况下,这些警告处于启用状态。但是,如果
指定了 OR 指令,即
意外的冲突数是一个错误,预期的冲突数
冲突没有报告,所以然后有
对冲突报告没有影响。%expect
%expect-rr
-W--warning
counterexamples
cex
提供冲突的反例。请参阅反例的生成。 反例需要时间来计算。该选项应为 开发人员在处理语法时使用;这几乎没有意义 在 CI 中使用它。-Wcex
dangling-alias
报告未绑定到令牌符号的字符串文本。
字符串文字,允许更好的错误消息,(太)自由 被 Bison 接受,这可能会导致静默错误。例如
%type <exVal> cond "condition"
未将“condition”定义为 —nonterminal 的字符串别名
符号没有字符串别名。它相当等价于cond
%nterm <exVal> cond %token <exVal> "condition"
即,它为''令牌提供类型。"condition"exVal
此外,由于不需要定义字符串别名,因此 “”而不是“”将不会被报告。"baz""bar"
该选项可捕获这些情况。上-Wdangling-alias
%token BAR "bar" %type <ival> foo "foo" %% foo: "baz" {}
'' 报告bison -Wdangling-alias
warning: string literal not attached to a symbol | %type <ival> foo "foo" | ^~~~~ warning: string literal not attached to a symbol | foo: "baz" {} | ^~~~~
deprecated
已弃用的构造,其支持将在 将来的 野牛。
empty-rule
没有 .请参阅空规则。禁用者
默认值,但通过使用 启用,除非指定。%empty
%empty
-Wno-empty-rule
midrule-values
警告已设置但未在任何操作中使用的中间规则值
父规则。
例如,警告未在以下情况下使用:$2
exp: '1' { $$ = 1; } '+' exp { $$ = $1 + $4; };
还要警告已使用但未设置的中间规则值。
例如,警告以下 midrule 操作中的未设置:$$
exp: '1' { $1 = 1; } '+' exp { $$ = $2 + $4; };
默认情况下不启用这些警告,因为它们有时会证明
是使用 Yacc 结构的现有语法中的误报,或者(其中是一些正整数)。$0
$-n
n
precedence
无用的优先级和关联性指令。默认情况下处于禁用状态。
例如,考虑以下语法:
%nonassoc "=" %left "+" %left "*" %precedence "("
%%
stmt: exp | "var" "=" exp ;
exp: exp "+" exp | exp "*" "number" | "(" exp ")" | "number" ;
野牛报告:
warning: useless precedence and associativity for "=" | %nonassoc "=" | ^~~
warning: useless associativity for "*", use %precedence | %left "*" | ^~~
warning: useless precedence for "(" | %precedence "(" | ^~~
人们将获得与以下指令完全相同的解析器:
%left "+" %precedence "*"
yacc
与 POSIX Yacc 不兼容。
other
上面未分类的所有警告。默认情况下,这些警告处于启用状态。
提供此类别只是为了完整起见。前途 Bison 的版本可能会将警告从此类别移动到新的、更具体的警告 类别。
all
除 和 之外的所有警告。counterexamples
dangling-alias
yacc
none
关闭所有警告。
error
请参阅下文。-Werror
可以通过在类别名称前加上“”来关闭类别。为 实例,将隐藏有关 POSIX Yacc 不兼容。no--Wno-yacc
-Werror
将每个启用的警告转换为错误,除非它们是 显式禁用。category-Wno-error=category
-Werror=category
启用 的警告,并将它们视为错误。category
category与 相同,不同之处在于 它不能以“”为前缀(见上文)。--warningsno-
请注意,'' 和 '' 运算符的优先级是这样的 以下命令不等效,因为第一个命令不会处理 S/R 冲突为错误。=,
$ bison -Werror=yacc,conflicts-sr input.y $ bison -Werror=yacc,error=conflicts-sr input.y
-Wno-error
不要将启用的警告转换为错误,除非 它们由 显式启用。category-Werror=category
-Wno-error=category
停用此 .但是,警告 此选项不会禁用或启用本身。category
--color
等效于 。--color=always
--color=when
控制诊断是否着色,具体取决于:when
always
yes
启用彩色诊断。
never
no
禁用彩色诊断。
auto (default)
tty
如果输出设备是 tty,则诊断将被着色,即当 输出直接转到文本屏幕或终端仿真器窗口。
--style=file
指定着色时要使用的 CSS 样式。它有效果 仅当选项有效时。该文件提供了一个很好的示例,从中定义 您自己的样式文件。有关更多信息,请参阅 libtextstyle 的文档 详。file--colorbison-default.css
Next: 输出文件, Previous: 诊断, Up: Bison Options [Contents][Index]
更改生成的解析器的选项。
在分析器实现文件中,如果
它尚未定义,因此编译调试工具。
请参阅跟踪分析器。YYDEBUG
其中每个都等同于“” (请参阅 %define 摘要)。请注意,分隔符是 : 的一部分,对应于 ''、'' 和 ''。%define name valuevalue-Dapi.value.type=union-Dapi.value.type={union}-Dapi.value.type="union"%define api.value.type union%define api.value.type {union}%define api.value.type "union"
Bison 处理多个定义,如下所示:name
%define
name%define
name%define
name您应该避免在您的
创建文件,除非您确信悄悄地忽略它是安全的
任何可能添加到语法文件中的冲突。-F--force-define%define
为生成的解析器指定编程语言,就像已指定一样(请参阅 Bison 声明摘要)。目前支持
语言包括C,C++,D和Java。 不区分大小写。%language
language
假装已指定。参见野牛宣言摘要。%locations
假装已指定(参见 Bison Declaration Summary)。该选项由 POSIX 指定。当POSIX时
兼容性不是必需的,而是
更好的选项(请参阅同一程序中的多个解析器)。%name-prefix "prefix"
-p-Dapi.prefix=prefix
不要在解析器中放置任何预处理器命令
实现文件。通常,Bison 将它们放在解析器中
实现文件,以便 C 编译器和调试器将
将错误与源文件(语法文件)相关联。此选项
导致它们将错误与解析器实现文件相关联,
将其视为独立的源文件。#line
指定要使用的骨架,类似于(请参阅 Bison Declaration Summary)。%skeleton
如果不包含 ,则为骨架的名称
文件。
如果是这样,则是一个绝对文件名或相对于
当前工作目录。
这类似于大多数 shell 解析命令的方式。file/
filefile
假装已指定。参见野牛宣言摘要。%token-table
行为更像是传统命令:yacc
#define
enum
POSIXLY_CORRECT
yyerror
yylex
int yylex (void); void yyerror (const char *);
作为 Bison 扩展,需要 和 的其他参数被纳入
帐户。您可以使用 ''(由 POSIX 指定)或 ''(Bison 扩展名)禁用 的原型。同样,对于 .%pure-parser
%locations
%lex-param
%parse-param
yyerror
#define
yyerror yyerror#define
YYERROR_IS_DECLAREDyylex
/ 选项旨在与传统的 Yacc 语法。此选项仅对默认的 C 框架有意义。如果您的语法使用 Bison 扩展,则 Bison 不能 与 Yacc 兼容,即使指定了此选项。-y--yaccyacc.c
因此,以下 shell 脚本可以替换 Yacc 和 Bison
发行版包含这样的脚本,以便与
POSIX:yacc
#! /bin/sh bison -y "$@"
Previous: Tuning the Parser, Up: Bison Options [Contents][Index]
控制输出的选项。
假装指定了,即写入一个额外的输出文件
包含语法中定义的标记类型名称的定义,如
以及其他一些声明。参见野牛宣言摘要。%header
Bison 3.8 之前选项的历史名称。--header
这与 except 不接受参数相同,因为 POSIX Yacc 要求可以 与其他短选项捆绑在一起。--header-dfile-d
假装已指定,即指定要使用 的前缀
对于所有 Bison 输出文件名。参见野牛宣言摘要。%file-prefix
编写一个额外的输出文件,其中包含逗号的详细描述 其中的分离列表:things
state
语法描述、冲突(已解决和未解决)以及 解析器的自动机。
itemset
暗示并增强对自动机的描述
每个状态的完整项目集,而不仅仅是其核心。state
lookahead
暗示并增强对自动机的描述
每个规则的前瞻集。state
solved
意味 着。解释冲突是如何解决的,这要归功于
优先级和关联性指令。state
counterexamples
cex
寻找冲突的反例。请参阅反例的生成。 反例需要时间来计算。该选项应为 开发人员在处理语法时使用;这几乎没有意义 在 CI 中使用它。-rcex
all
启用所有项目。
none
不要生成报告。
指定详细描述。file
假装已指定,即写入额外的输出
包含详细语法描述的文件和
解析 器。参见野牛宣言摘要。%verbose
指定 for 分析器实现文件。file
其他输出文件的名称由 在 AND 选项下进行了描述。file-v-d
输出解析器自动机的图形表示,计算公式为
Bison,Graphviz DOT 格式。 是可选的。如果省略且语法文件为 ,则输出文件将为 。file
foo.yfoo.gv
输出由 Bison 计算的解析器自动机的 XML 报告。 是可选的。
如果省略且语法文件为 ,则输出文件将为 。file
foo.yfoo.xml
在输出中写入文件路径时将前缀替换为 文件。oldnew
Next: Yacc 库, Previous: Bison Options, Up: Ininvokeking Bison [Contents][Index]
以下是按长选项字母顺序排列的选项列表,以帮助您找到 相应的空头期权和指令。
多头期权 | 空头期权 | 野牛指令 |
---|---|---|
--color[=when] | ||
--debug | -t | %debug |
--define=name[=value] | -D name[=value] | %define name [value] |
--feature[=features] | -f [features] | |
--file-prefix-map=old=new | -M old=new | |
--file-prefix=prefix | -b prefix | %file-prefix "prefix" |
--force-define=name[=value] | -F name[=value] | %define name [value] |
--graph[=file] | -g [file] | |
--header=[file] | -H [file] | %header ["file"] |
--help | -h | |
--html[=file] | ||
--language=language | -L language | %language "language" |
--locations | %locations | |
--name-prefix=prefix | -p prefix | %name-prefix "prefix" |
--no-lines | -l | %no-lines |
--output=file | -o file | %output "file" |
--print-datadir | ||
--print-localedir | ||
--report-file=file | ||
--report=things | -r things | |
--skeleton=file | -S file | %skeleton "file" |
--style=file | ||
--token-table | -k | %token-table |
--update | -u | |
--verbose | -v | %verbose |
--version | -V | |
--warnings[=category] | -W [category] | |
--xml[=file] | -x [file] | |
--yacc | -y | %yacc |
Yacc 库包含 and 函数的默认实现。这些默认实现通常不是
有用,但 POSIX 需要它们。要使用 Yacc 库,请链接您的程序
替换为选项。请注意,Bison 对 Yacc 的实现
库是根据 GNU 通用公共许可证的条款分发的
(参见 GNU 通用公共许可证)。yyerror
main
-ly
如果使用 Yacc 库的函数,则应声明如下:yyerror
yyerror
int yyerror (char const *);
这将被忽略。int
yyerror
Yacc 库函数的实现是:main
int main (void) { setlocale (LC_ALL, ""); return yyparse (); }
因此,如果您使用它,则会启用国际化支持(例如,错误
消息被翻译),并且您的函数应具有
以下类型签名:yyparse
int yyparse (void);
除了 C 之外,Bison 还可以用 C++、D 和 Java 生成解析器。本章 致力于这些语言。读者应该了解如何 野牛作品;如果您不这样做,请先阅读介绍性章节。
下一篇: D 解析器, 向上: 用其他语言编写的解析器 [目录][索引]
C++ 中的 Bison 解析器是一个对象,是类的实例。yy::parser
下一篇: C++ Bison 接口,上一篇: C++ 解析器 [内容][索引]
本教程关于 C++ 解析器基于一个简单的、独立的 例。7 以下各节是参考 带有 C++ 的 Bison 手册,最后一个显示了一个完整的示例 (请参阅完整的 C++ 示例)。
为了看起来更好,我们的示例将采用 C++ 14。不是必需的:野牛 支持原始 C++ 98 标准。
Bison 文件由三个部分组成。在第一部分,序幕中,我们首先 确保我们运行一个足够新的 Bison 版本,并且我们 生成 C++。
%require "3.2" %language "c++"
让我们直接进入中间部分:语法。我们的输入是 简单的字符串列表,我们在解析完成后显示。
%%
result: list { std::cout << $1 << '\n'; } ;
%nterm <std::vector<std::string>> list;
list: %empty { /* Generates an empty string list */ } | list item { $$ = $1; $$.push_back ($2); } ;
我们使用字符串向量作为语义值!使用真正的 C++ 对象 作为语义值——而不仅仅是 POD——我们不能依赖 Bison 的并集 默认情况下使用来存储它们,我们需要变体(请参阅 C++ 变体):
%define api.value.type variant
显然,规则需要打印字符串的向量。
在序言中,我们补充道:result
%code { // Print a list of strings. auto operator<< (std::ostream& o, const std::vector<std::string>& ss) -> std::ostream& { o << '{'; const char *sep = "";
for (const auto& s: ss) { o << sep << s; sep = ", "; }
return o << '}'; } }
您可能希望将其移动到命名空间中,以避免将其泄漏
默认命名空间。我们建议您保持操作简单,并且
将详细信息移动到辅助函数中,就像我们对 .yy
operator<<
我们的字符串列表将由两种类型的项目构建:数字和 字符串:
%nterm <std::string> item; %token <std::string> TEXT; %token <int> NUMBER;
item: TEXT | NUMBER { $$ = std::to_string ($1); } ;
在 的情况下,隐式默认操作适用:。TEXT
$$ = $1
我们的扫描仪值得关注。传统的接口不是类型安全的:因为令牌种类和令牌值是
不相关,您可以返回带有字符串作为语义的 A
价值。为了避免这种情况,我们使用令牌构造函数(请参阅完整符号)。该指令:yylex
NUMBER
%define api.token.constructor
请求 Bison 生成函数 和 ,但也生成 ,用于输入的结尾。make_TEXT
make_NUMBER
make_YYEOF
我们的扫描仪一切就绪:
%code { namespace yy { // Return the next token. auto yylex () -> parser::symbol_type { static int count = 0; switch (int stage = count++) {
case 0: return parser::make_TEXT ("I have three numbers for you.");
case 1: case 2: case 3: return parser::make_NUMBER (stage);
case 4: return parser::make_TEXT ("And that's all!");
default: return parser::make_YYEOF ();
} } } }
在结语中,野牛语法文件的第三部分,我们留下了简单的 详细信息:错误报告功能和 main 功能。
%% namespace yy { // Report an error to the user. auto parser::error (const std::string& msg) -> void { std::cerr << msg << '\n'; } } int main () { yy::parser parse; return parse (); }
编译并运行!
$ bison simple.yy -o simple.cc $ g++ -std=c++14 simple.cc -o simple
$ ./simple {I have three numbers for you., 1, 2, 3, And that's all!}
下一篇: C++ 解析器接口, 上一篇: 一个简单的 C++ 示例, 上一篇: C++ 解析器 [内容][索引]
C++ 确定性解析器是使用 skeleton 指令选择的, ‘’.参见野牛宣言摘要。%skeleton "lalr1.cc"
运行时,将在 '' 中创建多个实体
命名空间。使用 '' 指令更改命名空间名称,
请参阅 %define 摘要。生成各种类
在以下文件中:bison
yy%define api.namespace
(假设语法文件的扩展名为“”。这 C++ 分析器类和辅助类型的声明。默认情况下,这 文件未生成(请参阅 Bison 声明摘要)。.yy
C++ 分析器类的实现。的基名和扩展名 这两个文件 ( 和 ) 遵循 与常规 C 解析器相同的规则(请参阅调用 Bison)。file.hhfile.cc
当两者都启用时生成,这
file 包含用于位置跟踪的类 和 的定义。如果出现以下情况,则不会生成它
“”是指定的,或者如果用户定义
使用位置。请参阅 C++ 位置值。%header
%locations
position
location
%define api.location.file none
无用的遗留文件。要摆脱它,请使用“”或 新。%require "3.2"
所有这些文件都使用 Doxygen 进行记录;为一个
完整准确的文档。doxygen
输出文件并声明和
在命名空间中定义解析器类。类名默认值
更改为 ,但可以使用 '' 进行更改。下面详细介绍了此类的接口。它可以是
使用该功能扩展:其语义略有
更改,因为它描述了 Parser 类的附加成员,并且
其构造函数的附加参数。file.hhfile.ccyy
parser
%define api.parser.class
{name}%parse-param
包含(仅)枚举的结构,
它定义了令牌。要引用令牌,请使用 。扫描程序可以使用 '' 来“导入”令牌枚举(请参阅 Calc++ 扫描程序)。token_kind_type
FOO
yy::parser::token::FOO
typedef
yy::parser::token token;
令牌种类的枚举。它的枚举器是从
令牌名称,可能具有令牌前缀
(参见 api.token.prefix
):
/// Token kinds. struct token { enum token_kind_type { YYEMPTY = -2, // No token. YYEOF = 0, // "end of file" YYerror = 256, // error YYUNDEF = 257, // "invalid token" PLUS = 258, // "+" MINUS = 259, // "-" [...] VAR = 271, // "variable" NEG = 272 // NEG }; }; /// Token kind, as returned by yylex. typedef token::token_kind_type token_kind_type;
此类派生自 。抛出它的实例
从扫描程序或从引发分析错误的操作。这是
等同于首次调用以报告位置和
消息的语法错误,然后调用输入
错误恢复模式。但与之相反,只能是
从用户操作(即写入操作本身)调用,
可以从从用户操作调用的函数中引发异常。std::runtime_error
error
YYERROR
YYERROR
生成新的分析器对象。没有争论,除非 '' 被使用。%parse-param {type1 arg1}
const location_type&
l, const std::string&
m) ¶const std::string&
m) ¶实例化语法错误异常。
运行句法分析,成功时返回 0,否则返回 1。双
例程是等效的,更像 C++。operator()
整个函数被包装在一个 / 块中,因此
当抛出异常时,将调用 S 来释放
前瞻符号,以及推送到堆栈上的符号。try
catch
%destructor
生成的解析器中与异常相关的代码受 CPP 防护保护
() 并在不支持异常时禁用(即传递给 C++ 编译器)。#if
-fno-exceptions
std::ostream&
o) ¶获取或设置用于跟踪分析的流。它默认为 。std::cerr
获取或设置跟踪级别(积分)。目前它的值是 0,无跟踪,或非零,完全跟踪。
const location_type&
l, const std::string&
m) ¶const std::string&
m) ¶此成员函数的定义必须由用户提供: Parser 使用它来报告在 处发生的解析器错误,如 所述。如果未启用位置跟踪,则使用第二个签名。lm
Bison 支持两种不同的方法来处理 C++ 中的语义值。一是 与 C 接口类似,并且依赖于联合。正如 C++ 从业者所知道的, 联合在 C++ 中很不方便,因此提供了另一种方法, 基于变体。
该指令适用于 C,请参阅联盟宣言。在
特别是它产生一个真正的,它有几个特定的
C++ 中的功能。%union
union
yy::parser::value_type
YYSTYPE
因为对象必须通过指针存储,所以内存不是
自动回收:使用指令是
只有避免泄漏的手段。请参阅释放丢弃的符号。%destructor
Bison 提供了基于变体的语义值实现 C++。这缓解了上一节中报告的所有限制, 特别是,可以在没有指针的情况下使用对象类型。
若要启用基于变体的语义值,请将变量设置为(请参阅 %define 摘要)。然后被忽略;不要使用 的字段名称来“键入”符号,而是使用真正的类型。%define
api.value.type
variant
%union
%union
例如,而不是:
%union { int ival; std::string* sval; } %token <ival> NUMBER; %token <sval> STRING;
写:
%token <int> NUMBER; %token <std::string> STRING;
STRING
不再是指针,这应该相当简化用户
语法和扫描仪(特别是内存)中的操作
管理)。
由于 C++ 具有析构函数,并且习惯上专门支持统一打印值,因此变体也
通常简化 Bison 打印机和析构函数。operator<<
变体比工会更严格。当基于工会时,您可以玩任何
肮脏的游戏,比如说存储一个,读取一个,然后把一个存储在里面。这不再是
变体可能:它们必须初始化,然后分配给,并且
最终,被摧毁。事实上,野牛变种禁止使用
替代类型,例如“”或“”,甚至
在 midrule 操作中。必须使用类型化的中间规则操作
(请参阅类型化 Midrule 操作)。yylval
int
char*
double
$<int>2$<std::string>$
const T&
t)¶仅在 C++ 98/C++03 中可用。默认构造/复制构造来自 .返回对实际值可能存储位置的引用。 要求尚未初始化变体。t
U&&...
u) ¶仅在 C++11 及更高版本中可用。从以下位置构建类型的变体
可变参数转发引用 。T
u...
警告:我们不使用 Boost.Variant,原因有两个。首先,它
在用户机器上要求 Boost 似乎是不可接受的(即,
生成的解析器将在其上编译的机器,而不是
已运行)。其次,对于每个可能的语义值,
Boost.Variant 不仅存储值,还存储一个指定其
类型。但是解析器已经“知道”语义值的类型,所以
这将是重复信息。bison
我们也不使用 C++ 17:我们希望支持所有
C++ 标准,当然也存储要记录的标签
当前类型。std::variant
std::variant
因此,我们开发了类型标签为外部的轻量级变体(所以
他们实际上真的很像C++)。有许多
变体(当前实现)的局限性:unions
double
double
据我们所知,这些限制是可以缓解的。一切所需 是一些时间和/或一些有才华的C++黑客愿意为Bison做出贡献。
下一篇: C++ 解析器上下文, 上一篇: C++ 语义值, 上一篇: C++ 解析器 [内容][索引]
使用该指令时,C++ 解析器支持
位置跟踪,请参阅跟踪位置。%locations
默认情况下,两个辅助类定义一个 ,一个点
在文件中,和 ,由一对 S 组成的范围(可能跨越多个文件)。如果定义了变量,则这些类将不会
生成,并将使用用户定义的类型。position
location
position
%define
api.location.type
position
文件名的基本类型。默认值为 。
请参阅 api.filename.type
,以更改其定义。const std::string
用于存储行号和列号的类型。定义为 .int
filename_type*
file = nullptr, counter_type
line = 1, counter_type
col = 1) ¶创建一个表示给定点的点。请注意,这是
销毁时未回收:内存管理必须
在其他地方处理。position
file
position
filename_type*
file = nullptr, counter_type
line = 1, counter_type
col = 1) ¶将位置重置为给定值。
文件的名称。它将始终作为指针(解析器)处理 永远不会复制或取消分配它。
该行,从 1 开始。
counter_type
高 = 1)¶如果不是 null,则按行前进,重置 列号。生成的行号不能小于 1。heightheight
该列,从 1 开始。
counter_type
width = 1) ¶按列前进,不更改行号。这 生成的列号不能小于 1。width
counter_type
width) ¶counter_type
宽度)¶counter_type
width) ¶counter_type
宽度)¶各种形式的句法糖。columns
const position&
that) ¶const position&
that) ¶是否 和 表示相等/不同的位置。*this
that
std::ostream&
o, const position&
p) ¶报告如下: '',或 '' 如果为 null。pofile:line.columnline.columnfile
location
const position&
begin, const position&
end) ¶从范围的终结点创建一个。Location
const position&
pos = position()) ¶filename_type*
文件、counter_type
行、counter_type
列) ¶创建一个表示位于给定点的空范围。Location
filename_type*
file = nullptr, counter_type
line = 1, counter_type
col = 1) ¶在给定值处将位置重置为空范围。
counter_type
宽度)¶counter_type
宽度) ¶counter_type
宽度)¶counter_type
宽度) ¶各种形式的句法糖。columns
const location&
end) ¶const location&
end) ¶连接两个位置:从第一个位置开始,到 第二个的位置。
转到 。begin
end
const location&
that) ¶const location&
that) ¶是否 和 表示相等/不同范围
位置。*this
that
std::ostream&
o, const location&
p) ¶报告,处理特殊情况,例如:未定义或相等的文件名/行或列。pofilename
当两者都启用时,Bison 将生成
附加文件:.如果您不使用外部位置
,您可以避免使用 '' 创建它。%header
%locations
location.hh%define
api.location.file none
但是,例如,如果您的解析器构建了一个摘要,则此文件很有用
用位置装饰的语法树:您可以独立于 Bison 的解析器使用 Bison 的类型。您可以以不同的方式命名文件,
例如,“'”:这个名字
可以有目录组件,甚至可以是绝对的。位置的方式
文件包含在内,由 控制。location
%define api.location.file "include/ast/location.hh"api.location.include
这样,就可以让多个解析器共享同一个位置 文件。
例如,在 中生成文件:src/foo/parser.yyinclude/ast/loc.hh
// src/foo/parser.yy %locations %define api.namespace {foo} %define api.location.file "include/ast/loc.hh" %define api.location.include {<ast/loc.hh>}
并将其用于:src/bar/parser.yy
// src/bar/parser.yy %locations %define api.namespace {bar} %code requires {#include <ast/loc.hh>} %define api.location.type {bar::location}
支持绝对文件名;它是安全的
将标志传递给 for 。生成的文件不会
引用此绝对路径,感谢 ''。对于编译器来说,将“”添加到您的遗嘱中就足够了
找到。Makefile-Dapi.location.file='"$(top_srcdir)/include/ast/loc.hh"'bison
src/foo/parser.yy%define
api.location.include {<ast/loc.hh>}-I
$(top_srcdir)/includeCPPFLAGS
ast/loc.hh
您可以使用变量来指定自己的类型,而不是使用内置类型:%define
api.location.type
%define api.location.type {LocationType}
您的要求是:LocationType
@$
@$.begin = @1.begin; @$.end = @N.end; // The location of last right-hand side symbol.
所以必须有可复制的和成员;begin
end
在具有多个 C++ 解析器的程序中,还可以使用该变量共享一组通用的内置变量
和 的定义。例如,一个
解析器可能使用:%define
api.location.type
position
location
master/parser.yy
%header %locations %define api.namespace {master::}
生成 AND 文件,供其他解析器重用,如下所示:master/position.hhmaster/location.hh
%define api.location.type {master::location} %code requires { #include <master/location.hh> }
使用“”时(请参阅语法错误报告函数 yyreport_syntax_error
),用户必须定义以下函数。%define parse.error custom
const context_type&
ctx) const
¶向用户报告语法错误。是否使用取决于
用户。yyerror
使用以下类型和函数生成错误消息。
捕获语法错误情况的类型。
所有语法符号、标记和非终端的枚举。其 枚举器是从符号名称伪造的:
struct symbol_kind { enum symbol_kind_type { S_YYEMPTY = -2, // No symbol. S_YYEOF = 0, // "end of file" S_YYERROR = 1, // error S_YYUNDEF = 2, // "invalid token" S_PLUS = 3, // "+" S_MINUS = 4, // "-" [...] S_VAR = 14, // "variable" S_NEG = 15, // NEG S_YYACCEPT = 16, // $accept S_exp = 17, // exp S_input = 18 // input }; }; typedef symbol_kind::symbol_kind_t symbol_kind_type;
const
¶“意外”标记:导致语法错误的前瞻。
const
¶导致语法错误的 lookahead 标记的符号类型。如果没有前瞻,则返回。symbol_kind::S_YYEMPTY
const
¶语法错误的位置(前瞻错误的位置)。
symbol_kind_type
argv[]
, int
argc) const
¶填充预期的标记,其中从不包含 、 或 。argvsymbol_kind::S_YYEMPTY
symbol_kind::S_YYERROR
symbol_kind::S_YYUNDEF
永远不要把更多的元素放进去,在成功上
返回存储在 中的令牌数。如果还有更多
预期的令牌比 ,填满并返回
0. 如果没有预期的令牌,则也返回 0,但设置为 。argcargvargvargcargvargcargv[0]
symbol_kind::S_YYEMPTY
如果为 null,则返回存储所有可能文件所需的大小
值,它总是小于 。argvYYNTOKENS
symbol_kind_t
符号) const
¶其种类为 的符号的名称,可能已翻译。symbol
返回 when is 。std::string
parse.error
verbose
自定义语法错误函数如下所示。此实现是 不适合国际化,请参阅示例以获取更好的替代方案。c/bistromathic
void yy::parser::report_syntax_error (const context& ctx) { int res = 0; std::cerr << ctx.location () << ": syntax error"; // Report the tokens expected at this point. { enum { TOKENMAX = 5 }; symbol_kind_type expected[TOKENMAX]; int n = ctx.expected_tokens (ctx, expected, TOKENMAX); for (int i = 0; i < n; ++i) std::cerr << i == 0 ? ": expected " : " or " << symbol_name (expected[i]); } // Report the unexpected token. { symbol_kind_type lookahead = ctx.token (); if (lookahead != symbol_kind::S_YYEMPTY) std::cerr << " before " << symbol_name (lookahead)); } std::cerr << '\n'; }
您仍然必须提供一个函数,例如用于
报告内存耗尽。yyerror
下一篇: 完整的 C++ 示例, 上一篇: C++ 解析器上下文, 上一篇: C++ 解析器 [内容][索引]
解析器通过调用 来调用扫描程序。与 C 相反
解析器,C++ 解析器始终是纯的:使用
'' 指令。实际的接口取决于您使用的是联合还是变体。yylex
%define api.pureyylex
生成的解析器应具有以下原型。yylex
value_type*
yylval, location_type*
yylloc, type1 arg1, ...) ¶value_type*
yylval, type1 arg1, ...) ¶返回下一个令牌。它的种类是返回值、语义值和 location(如果启用)是 和 。调用 '' 产生额外的参数。yylvalyylloc%lex-param {type1 arg1}
请注意,使用变体时,接口是相同的,
但处理方式不同。yylex
yylval
Lex 扫描程序中基于联合的常规代码通常如下所示:
[0-9]+ { yylval->ival = text_to_int (yytext); return yy::parser::token::INTEGER; } [a-z]+ { yylval->sval = new std::string (yytext); return yy::parser::token::IDENTIFIER; }
使用变体,已经构建,但尚未构建
初始 化。因此,代码如下所示:yylval
[0-9]+ { yylval->emplace<int> () = text_to_int (yytext); return yy::parser::token::INTEGER; } [a-z]+ { yylval->emplace<std::string> () = yytext; return yy::parser::token::IDENTIFIER; }
或
[0-9]+ { yylval->emplace (text_to_int (yytext)); return yy::parser::token::INTEGER; } [a-z]+ { yylval->emplace (yytext); return yy::parser::token::IDENTIFIER; }
对于 和 ,解析器定义类型 ,并且
期望有以下原型。%define api.value.type variant
%define
api.token.constructor
symbol_type
yylex
返回一个完整的符号,聚合其类型(即传统的
返回的值),其语义值,可能还有其
位置。调用 '' yield
其他参数。yylex
%lex-param {type1 arg1}
一个“完整的符号”,将其种类、价值和(当 适用)位置。
const
¶这个符号的那种。
const
¶此符号的种类的名称。
返回 when is 。std::string
parse.error
verbose
对于每种令牌类型,Bison 都会按如下方式生成命名构造函数。
int
token, const value_type&
value, const location_type&
location) ¶int
token, const location_type&
location) ¶int
token, const value_type&
value) ¶int
token) ¶为令牌类型(包括
),其语义值(如果有的话)就足够了。通过 iff
位置跟踪已启用。tokenapi.token.prefix
valuevalue_typelocation
和 之间的一致性通过 .tokenvalue_typeassert
例如,给定以下声明:
%define api.token.prefix {TOK_} %token <std::string> IDENTIFIER; %token <int> INTEGER; %token ':';
您可以使用这些构造函数:
symbol_type (int token, const std::string&, const location_type&); symbol_type (int token, const int&, const location_type&); symbol_type (int token, const location_type&);
通过以下方式检查令牌类型和值类型之间的正确匹配;例如,“'”将中止。叫
构造函数是可取的(见下文),因为它们提供了更好的类型安全性
(例如,“”甚至不会编译),但symbol_type
当在运行时发现令牌类型时,构造函数可能会有所帮助,例如,assert
symbol_type (ID, 42)make_ID (42)
[a-z]+ { if (auto i = lookup_keyword (yytext)) return yy::parser::symbol_type (i, loc); else return yy::parser::make_ID (yytext, loc); }
请注意,可能会生成和编译类型不正确的代码 (例如'')。它将在运行时失败, 前提是启用了断言(即未传递 到编译器)。Bison 支持保证该类型的替代方案 不正确的代码甚至无法编译。事实上,它生成了名为 构造函数如下。symbol_type (':', yytext, loc)-DNDEBUG
const value_type&
value, const location_type&
location) ¶const location_type&
location) ¶const value_type&
value) ¶为令牌类型构建一个完整的终端符号(不是
包括 ),其语义值,如果它有,
是足够的.通过 iff
位置跟踪已启用。tokenapi.token.prefix
valuevalue_typelocation
例如,给定以下声明:
%define api.token.prefix {TOK_} %token <std::string> IDENTIFIER; %token <int> INTEGER; %token COLON; %token EOF 0;
Bison 生成:
symbol_type make_IDENTIFIER (const std::string&, const location_type&); symbol_type make_INTEGER (const int&, const location_type&); symbol_type make_COLON (const location_type&); symbol_type make_EOF (const location_type&);
应在扫描仪中使用,如下所示。
[a-z]+ return yy::parser::make_IDENTIFIER (yytext, loc); [0-9]+ return yy::parser::make_INTEGER (text_to_int (yytext), loc); ":" return yy::parser::make_COLON (loc); <<EOF>> return yy::parser::make_EOF (loc);
没有标识符的令牌是不可访问的:你不能简单地
使用诸如 ,它们必须声明为 ,
包括文件末尾令牌。':'
%token
本节演示如何使用 C++ 解析器,该解析器具有简单但完整的 例。这个例子应该在你的系统上可用,可以编译了, 在目录中。它专注于 使用Bison,因此各种C++类的设计非常 幼稚:没有访问器,没有成员封装等。我们将使用 Lex 扫描仪,更准确地说,是 Flex 扫描仪,用于演示各种 相互 作用。手写扫描仪实际上更容易使用。examples/c++/calc++
下一篇: Calc++ 解析驱动,向上:完整的 C++ 示例 [内容][索引]
当然,语法是专门用于算术的,一个单一的表达式,
可能前面有变量赋值。包含以下内容的环境
可能是预定义的变量,例如 和 ,是
与解析器交换。下面是有效输入的示例。one
two
three := 3 seven := one + two * three seven * seven
下一篇: Calc++ 解析器, 上一篇: Calc++ — C++ 计算器, 上一篇: 完整的 C++ 示例 [内容][索引]
为了支持与解析器(和扫描仪)的纯接口,该技术 的“解析上下文”很方便:一个包含所有 要交换的数据。因为,除了简单地启动解析之外,还有 是要执行的几个辅助任务(打开文件进行扫描, 实例化解析器等),我们建议转换简单解析 上下文结构转换为一个成熟的解析驱动程序类。
此驱动程序类在 中的声明如下。 第一部分包括 CPP 防护装置并导入所需的标准 库组件,以及 Parser 类的声明。driver.hh
#ifndef DRIVER_HH # define DRIVER_HH # include <string> # include <map> # include "parser.hh"
然后是扫描功能的声明。伟创力希望
的签名 要在宏中定义,并且
C++ 分析器期望声明它。我们可以将两者考虑如下。yylex
YY_DECL
// Give Flex the prototype of yylex we want ... # define YY_DECL \ yy::parser::symbol_type yylex (driver& drv) // ... and declare it for the parser's sake. YY_DECL;
然后,使用其最明显的成员声明该类。driver
// Conducting the whole scanning and parsing of Calc++. class driver { public: driver (); std::map<std::string, int> variables; int result;
主要例程当然是调用解析器。
// Run the parser on file F. Return 0 on success. int parse (const std::string& f); // The name of the file being parsed. std::string file; // Whether to generate parser debug traces. bool trace_parsing;
要封装与 Flex 扫描仪的协调,具有 成员函数打开和关闭扫描阶段。
// Handling the scanner. void scan_begin (); void scan_end (); // Whether to generate scanner debug traces. bool trace_scanning; // The token's location used by the scanner. yy::location location; }; #endif // ! DRIVER_HH
驱动程序 () 的实现很简单。driver.cc
#include "driver.hh" #include "parser.hh"
driver::driver () : trace_parsing (false), trace_scanning (false) { variables["one"] = 1; variables["two"] = 2; }
成员函数值得关注。parse
int driver::parse (const std::string &f) { file = f; location.initialize (&file); scan_begin (); yy::parser parse (*this); parse.set_debug_level (trace_parsing); int res = parse (); scan_end (); return res; }
下一篇: Calc++ 扫描仪, Previous: Calc++ 解析驱动, Up: 完整的 C++ 示例 [内容][索引]
语法文件首先询问 C++ 确定性 解析器框架,创建解析器头文件。因为 C++ 骨架换了好几次,需要你的版本更安全 设计了语法。parser.yy
%skeleton "lalr1.cc" // -*- C++ -*- %require "3.8.1" %header
因为我们的扫描仪只返回真正的令牌,从不返回简单的字符 (即,它返回 '',而不是 ''),我们可以避免转换。PLUS'+'
%define api.token.raw
此示例使用真正的 C++ 对象作为语义值,因此,我们
需要基于变体的语义值存储。确保我们
正确使用它,我们启用断言。充分受益于型式安全
而更自然的“符号”定义,我们启用.api.token.constructor
%define api.token.constructor %define api.value.type variant %define parse.assert
然后是语义值所需的声明/包含。 因为解析器使用解析驱动程序,并且相互,所以两者都希望 包括另一个的标题,这当然是疯狂的。这 将使用转发声明打破相互依赖关系。因为 驱动程序的标头需要有关分析器类的详细信息(在 特别是它的内部类型),它是解析器的标头,它将使用 驱动程序的转发声明。请参见%code 摘要。
%code requires { # include <string> class driver; }
驱动程序通过引用分析器和扫描程序传递。 这提供了一个简单但有效的纯接口,不依赖于 全局变量。
// The parsing context. %param { driver& drv }
然后我们请求位置跟踪。
%locations
使用以下两个指令启用解析器跟踪和详细错误 消息。但是,详细的错误消息可能包含不正确的内容 未启用前瞻校正时的信息(请参阅 LAC)。
%define parse.trace %define parse.error detailed %define parse.lac full
“”和“”之间的代码在文件中输出;它需要有关驱动程序的详细信息。%code {}*.cc
%code { # include "driver.hh" }
为每个符号提供了用户友好的名称。为了避免名称冲突,请执行以下操作
生成的文件(请参阅 Calc++ 扫描程序),前缀标记(请参阅 %define Summary)。TOK_
%define api.token.prefix {TOK_} %token ASSIGN ":=" MINUS "-" PLUS "+" STAR "*" SLASH "/" LPAREN "(" RPAREN ")" ;
由于我们使用基于变体的语义值,因此不使用 和 ,并且期望真正的类型,而不是类型
标签。%union
%token
%nterm
%type
%token <std::string> IDENTIFIER "identifier" %token <int> NUMBER "number" %nterm <int> exp
在错误期间启用内存释放不需要
恢复;例如,字符串的内存将由
常规析构函数。所有值都使用它们打印(请参阅打印语义值)。%destructor
operator<<
%printer { yyo << $$; } <*>;
语法本身很简单(参见位置跟踪计算器:ltcalc
)。
%% %start unit; unit: assignments exp { drv.result = $2; }; assignments: %empty {} | assignments assignment {}; assignment: "identifier" ":=" exp { drv.variables[$1] = $3; }; %left "+" "-"; %left "*" "/"; exp: "number" | "identifier" { $$ = drv.variables[$1]; } | exp "+" exp { $$ = $1 + $3; } | exp "-" exp { $$ = $1 - $3; } | exp "*" exp { $$ = $1 * $3; } | exp "/" exp { $$ = $1 / $3; } | "(" exp ")" { $$ = $2; } %%
最后,成员函数报告错误。error
void yy::parser::error (const location_type& l, const std::string& m) { std::cerr << l << ": " << m << '\n'; }
下一篇: Calc++ 顶层, 上一篇: Calc++ 解析器, 上一篇: 完整的 C++ 示例 [内容][索引]
除了标准标头外,Flex 扫描仪还包括驱动程序的 然后解析器获取定义的标记集。
%{ /* -*- C++ -*- */ # include <cerrno> # include <climits> # include <cstdlib> # include <cstring> // strerror # include <string> # include "driver.hh" # include "parser.hh" %}
由于我们的计算器没有类似功能,因此我们不需要 .我们不需要 和 函数
或者,我们解析一个实际的文件,这不是一个交互式会话
用户。最后,我们启用扫描仪跟踪。#include
yywrap
unput
input
%option noyywrap nounput noinput batch debug
以下函数将方便转换表示数字的字符串
变成令牌。NUMBER
%{ // A number symbol corresponding to the value in S. yy::parser::symbol_type make_NUMBER (const std::string &s, const yy::parser::location_type& loc); %}
缩写允许更易读的规则。
id [a-zA-Z][a-zA-Z_0-9]* int [0-9]+ blank [ \t\r]
以下段落足以准确跟踪位置。每次调用时,起始位置都会移动到结束位置。
然后,当匹配模式时,其宽度将添加到结束列中。什么时候
匹配行的末端,调整结束光标,每次空白
匹配时,开始光标将移动到结束光标上以有效忽略
标记前面的空白。评论将得到平等对待。yylex
%{ // Code run each time a pattern is matched. # define YY_USER_ACTION loc.columns (yyleng); %}
%%
%{ // A handy shortcut to the location held by the driver. yy::location& loc = drv.location; // Code run each time yylex is called. loc.step (); %}
{blank}+ loc.step (); \n+ loc.lines (yyleng); loc.step ();
规则很简单。驱动程序用于报告错误。
"-" return yy::parser::make_MINUS (loc); "+" return yy::parser::make_PLUS (loc); "*" return yy::parser::make_STAR (loc); "/" return yy::parser::make_SLASH (loc); "(" return yy::parser::make_LPAREN (loc); ")" return yy::parser::make_RPAREN (loc); ":=" return yy::parser::make_ASSIGN (loc); {int} return make_NUMBER (yytext, loc); {id} return yy::parser::make_IDENTIFIER (yytext, loc);
. { throw yy::parser::syntax_error (loc, "invalid character: " + std::string(yytext)); }
<<EOF>> return yy::parser::make_YYEOF (loc); %%
您应该在解析器和扫描程序中保持规则简单。 然后,从辅助函数中抛出可以非常方便地报告错误。
yy::parser::symbol_type make_NUMBER (const std::string &s, const yy::parser::location_type& loc) { errno = 0; long n = strtol (s.c_str(), NULL, 10); if (! (INT_MIN <= n && n <= INT_MAX && errno != ERANGE)) throw yy::parser::syntax_error (loc, "integer is out of range: " + s); return yy::parser::make_NUMBER ((int) n, loc); }
最后,因为与扫描程序相关的驱动程序的成员函数依赖于 在扫描仪的数据上,在此文件中实现它们更简单。
void driver::scan_begin () { yy_flex_debug = trace_scanning; if (file.empty () || file == "-") yyin = stdin; else if (!(yyin = fopen (file.c_str (), "r"))) { std::cerr << "cannot open " << file << ": " << strerror (errno) << '\n'; exit (EXIT_FAILURE); } }
void driver::scan_end () { fclose (yyin); }
Previous: Calc++ 扫描仪, Up: 一个完整的 C++ 示例 [内容][索引]
顶级文件 ,没有问题。calc++.cc
#include <iostream> #include "driver.hh"
int main (int argc, char *argv[]) { int res = 0; driver drv; for (int i = 1; i < argc; ++i) if (argv[i] == std::string ("-p")) drv.trace_parsing = true; else if (argv[i] == std::string ("-s")) drv.trace_scanning = true; else if (!drv.parse (argv[i])) std::cout << drv.result << '\n'; else res = 1; return res; }
下一篇: Java 解析器, 上一篇: C++ 解析器, 上一篇: 用其他语言编写的解析器 [目录][索引]
D 解析器骨架是使用指令或 / 选项选择的。%language "D"
-L D--language=D
生成 D 解析器时,“' 将创建一个
名为包含
解析器实现。使用不带后缀的语法文件是
目前坏了。解析器实现文件的基名可以是
由指令或 / 选项更改。整个解析器实现
可以通过指令或 / 选项更改文件名。分析器实现文件
包含分析器的单个类。bison basename.ybasename.d.y%file-prefix
-b--file-prefix%output
-o--output
您可以使用 Ddoc 为生成的解析器创建文档。
D 中目前不支持 GLR 解析器。不要使用该指令。glr-parser
不能为 D 解析器生成头文件。不要使用指令或 / 选项。%header
-d--header
下一篇: D 位置值, 上一篇: D Bison 接口, 上一篇: D 解析器 [内容][索引]
语义类型由 和 '' 处理,类似于 C/C++ 解析器。在后一种情况下,并集
值由后端处理。在 D 中,联合可以保存类、结构、
等等,所以这个指令更类似于 C++ 中的“”。%union
%define api.value.type
union%define api.value.type
variant
D 解析器不支持 ,因为 语言
采用垃圾回收。解析器将尝试保存引用
根据需要尽可能短的时间进行语义值。%destructor
D 解析器支持 。类型为 的输出示例,其中 是解析器的调试输出:%printer
int
yyo
%printer { yyo.write($$); } <int>
使用该指令时,D 解析器支持位置
跟踪,请参阅跟踪位置。位置和位置
提供结构。%locations
Position
loc) ¶创建一个表示位于给定点的空范围。Location
Position
begin, Position
end) ¶从范围的终结点创建一个。Location
由位置表示为字符串的范围。
下一篇: D 解析器上下文接口, 上一篇: D 位置值, 上一篇: D 解析器 [内容][索引]
生成的分析器类的名称默认为 。可以使用“”更改前缀。
或者,使用 '' 给出一个
类的自定义名称。下面详细介绍了此类的接口。YYParser
YY
%define api.prefix%define api.parser.class {name}
默认情况下,分析器类具有公共可见性。要向
Parser 类、 和/或 。%define
api.parser.public
api.parser.abstract
api.parser.final
解析器类的超类和实现的接口可以是 使用 '' 和 '' 指令指定。%define api.parser.extends%define api.parser.implements
解析器类定义一个接口(请参阅 D 扫描仪接口)。除了此接口和
接口,所有其他成员和字段前面都带有 OR 前缀,以避免与用户代码发生冲突。Lexer
yy
YY
可以使用该指令扩展解析器类。默认情况下,该指令的每次出现都会添加一个 public
字段添加到解析器类,以及指向其构造函数的参数,该参数
自动初始化它们。%parse-param
使用嵌入的 '' 构建一个新的解析器对象。没有
参数,除非使用 s 和/或 s 和/或 s。%code lexer%param
%parse-param
%lex-param
Lexer
lexer, parse_param, ...) ¶使用指定的扫描程序生成新的分析器对象。没有
其他参数,除非 S 和/或 S 是
使用。%param
%parse-param
运行句法分析,否则返回成功。true
false
获取或设置用于生成详细错误消息的选项。这些只是 可用于 '', 这也会打开详细的错误消息。%define parse.error detailed
string
msg) ¶Location
loc, string
msg) ¶使用扫描仪的方法打印错误消息
实例正在使用中。和 参数为
仅当位置跟踪处于活动状态时才可用。yyerror
Location
Position
File
o) ¶获取或设置用于跟踪分析的流。它默认为 。stderr
int
l) ¶获取或设置跟踪级别。目前它的值为 0、无迹、 或非零,完全跟踪。
D 中的国际化与 C 中的国际化非常相似。The D
解析器用于翻译 Bison 消息。dgettext
要启用国际化,请使用 '' 和 import 和 from C 进行编译:-version ENABLE_NLS
-version YYENABLE_NLSbindtextdomain
textdomain
extern(C) char* bindtextdomain(const char* domainname, const char* dirname); extern(C) char* textdomain(const char* domainname);
main 函数应加载翻译目录,类似于示例:c/bistromathic
int main() { import core.stdc.locale; // Set up internationalization. setlocale(LC_ALL, ""); // Use Bison's standard translation catalog for error messages // (the generated messages). bindtextdomain("bison-runtime", BISON_LOCALEDIR); // For the translation catalog of your own project, use the // name of your project. bindtextdomain("bison", LOCALEDIR); textdomain("bison"); // usual main content ... }
对于用户消息翻译,用户必须实现“”函数。建议使用:string
_(const char* msg)gettext
%code imports { static if (!is(typeof(_))) { version(ENABLE_NLS) { extern(C) char* gettext(const char*); string _(const char* s) { return to!string(gettext(s)); } } } static if (!is(typeof(_))) { pragma(inline, true) string _(string msg) { return msg; } } }
解析器上下文提供用于在以下情况下生成错误报告的信息: 调用 ''。%define parse.error custom
包含所有语法符号、标记和 非终端。它的枚举器是从符号名称中伪造出来的。用 '' 获取符号名称。void toString(W)(W sink)
那种前瞻。返回时没有前瞻。null
前瞻的位置。
YYParser.SymbolKind[]
argv, int
argc) ¶填充预期的标记,其中从不包含 或 。argvSymbolKind.YYERROR
SymbolKind.YYUNDEF
永远不要把更多的元素放进去,在成功上
返回存储在 中的令牌数。如果还有更多
预期的令牌比 ,填满并返回
0. 如果没有预期的令牌,则也返回 0,但设置为 。argcargvargvargcargvargcargv[0]
null
如果为 null,则返回存储所有可能文件所需的大小
值,它总是小于 。argvYYNTOKENS
有两种可能的方法可以连接 Bison 生成的 D 解析器
使用扫描仪:扫描仪可以由 或 定义
在其他地方定义。无论哪种情况,扫描程序都必须实现解析器类的内部接口。此接口还
包含所有用户定义的令牌名称和预定义令牌的常量。%code lexer
Lexer
YYEOF
在第一种情况下,扫描仪类的主体被放置在块中。如果要从
Parser 构造函数添加到 scanner 构造函数中,用 ;它们在 s 之前传递给
构造 函数。%code lexer
%lex-param
%parse-param
在第二种情况下,扫描仪必须实现接口,
在解析器类中定义(例如,)。
然后,解析器对象的构造函数将接受一个对象
实现接口; 不在此使用
箱。Lexer
YYParser.Lexer
%lex-param
在这两种情况下,扫描程序都必须实现以下方法。
Location
loc, string
msg) ¶此方法由用户定义,用于发出错误消息。第一个 如果位置跟踪未处于活动状态,则省略参数。
返回下一个令牌。返回值的类型为 ,其
将种类、语义值和位置结合在一起。Symbol
YYParser.Context
ctx) ¶如果调用 ''(请参阅 Bison 声明部分),则解析器不再将语法错误消息传递给 ,而是通过调用函数将该任务委托给用户。%define parse.error customyyerror
reportSyntaxError
是否使用取决于用户。yyerror
下面是一个报告函数的示例(请参阅 D 解析器上下文接口)。
public void reportSyntaxError(YYParser.Context ctx) { stderr.write(ctx.getLocation(), ": syntax error"); // Report the expected tokens. { immutable int TOKENMAX = 5; YYParser.SymbolKind[] arg = new YYParser.SymbolKind[TOKENMAX]; int n = ctx.getExpectedTokens(arg, TOKENMAX); if (n < TOKENMAX) for (int i = 0; i < n; ++i) stderr.write((i == 0 ? ": expected " : " or "), arg[i]); } // Report the unexpected token which triggered the error. { YYParser.SymbolKind lookahead = ctx.getToken(); stderr.writeln(" before ", lookahead); } }
此实现不适合国际化,请参阅 更好的选择的例子。c/bistromathic
下面是一个 Bison 结构、变量和函数的表格,这些结构、变量和函数在 行动。
下一篇: D 完整符号, 上一篇: 用于 D 动作的特殊功能, 上一篇: D 解析器 [内容][索引]
通常,Bison 会为 D 生成一个拉取解析器。 以下 Bison 声明说您希望解析器是推送 解析器(参见 %define 摘要):
%define api.push-pull push
关于 D 拉取解析器接口的大多数讨论(参见 D 解析器接口)也适用于推送解析器接口。
生成推送解析器时,该方法是使用
以下签名:pushParse
Symbol
sym) ¶与拉取解析器的主要区别在于解析器
重复调用方法以解析每个令牌。这
如果 '' 或
使用 '' 声明(请参阅 %define 摘要)。pushParse
%define api.push-pull push%define api.push-pull both
该方法返回的值为下列值之一:、 或 。如果需要更多输入才能完成,则可能会返回此新值
解析输入。pushParse
ACCEPT
ABORT
PUSH_MORE
PUSH_MORE
如果定义为 ,则生成的解析器
类也将实现该方法。此方法的主体是
重复调用扫描程序,然后传递获取的值的循环
从扫描仪到方法。api.push-pull
both
parse
pushParse
上一篇: D 解析器, 上一篇: 用其他语言编写的解析器 [目录][索引]
Java 解析器框架是使用指令或 / 选项选择的。%language "Java"
-L java--language=java
生成 Java 解析器时,“' 将创建一个
单个 Java 源文件,其中包含
解析器实现。使用不带后缀的语法文件是
目前坏了。解析器实现文件的基名可以是
由指令或 / 选项更改。整个解析器实现
可以通过指令或 / 选项更改文件名。分析器实现文件
包含分析器的单个类。bison basename.ybasename.java.y%file-prefix
-b--file-prefix%output
-o--output
您可以使用 Javadoc 为生成的解析器创建文档。
与 C 解析器相反,Java 解析器不使用全局变量;国家
的解析器始终是解析器类实例的本地变量。
因此,所有 Java 解析器都是“纯”的,在 Java 中使用时该指令不执行任何操作。%define api.pure
Java 目前不支持 GLR 解析器。不要使用该指令。glr-parser
不能为 Java 解析器生成头文件。不要使用指令或 // 选项。%header
-d-H--header
目前,始终编译了对跟踪的支持。因此,
'' 和 '' 指令以及 / 和 / 选项
没有效果。这将来可能会改变,以消除
生成的解析器,因此如果
需要。此外,将来该指令可能会启用
用于访问令牌名称和代码的公共接口。%define parse.trace%token-table-t--debug-k--token-table%define parse.trace%token-table
从 Java 编译器收到“代码太大”错误意味着代码命中 Java 类文件的每个方法限制为 64KB 字节码。尝试 减少操作和静态初始值设定项中的代码量;否则 报告错误,以便改进解析器框架。
Next: Java 位置值, Previous: Java Bison Interface, Up: Java 解析器 [Contents][索引]]
Java 解析器中没有指令。取而代之的是,语义
值的类型(类名)应在 OR 指令中指定:%union
%nterm
%token
%nterm <Expression> expr assignment_expr term factor %nterm <Integer> number
默认情况下,语义堆栈声明为具有成员,
这意味着您指定的类类型可以是任何类。
为了提高解析器的类型安全性,可以声明 common
所有语义值的超类,使用 ''
命令。例如,在以下声明之后:Object
%define api.value.type
%define api.value.type {ASTNode}
any ,或指定语义类型
它不是 的子类,将导致编译时错误。%token
%nterm
%type
ASTNode
指令中使用的类型可以使用包名称进行限定。 Java V1.5 或更高版本接受基元数据类型。注意 在这种情况下,将使用 Java 1.5 的自动装箱功能。 不能使用泛型类型;这是由于 Bison 的实现,并且可能会在将来的版本中发生变化。
Java 解析器不支持 ,因为该语言
采用垃圾回收。解析器将尝试保存引用
根据需要尽可能短的时间进行语义值。%destructor
Java 解析器不支持 ,因为它可用于打印语义值。但是,这可能会改变
(以向后兼容的方式)在 Bison 的未来版本中。%printer
toString()
Next: Java 解析器接口, 上一篇: Java 语义值, 上一篇: Java 解析器 [Contents][Index]
使用该指令时,Java 解析器支持
位置跟踪,请参阅跟踪位置。辅助用户定义
class 定义一个位置,一个文件中的单个点;野牛本身
定义一个表示位置的类,该范围由一对
位置(可能跨越多个文件)。location 类是内部
解析器的类;默认情况下,该名称是,也可能是
使用 重命名。%locations
Location
%define api.location.type {class-name}
位置类将位置视为完全不透明的值。
默认情况下,类名为 ,但可以更改
跟。此类必须
由用户提供。Position
%define api.position.type {class-name}
位置
位置) ¶创建一个表示位于给定点的空范围。Location
位置
开始,位置
结束)¶从范围的终结点创建一个。Location
打印由位置表示的范围。为此要起作用
正确地,position 类应适当地重写 and 方法。equals
toString
Next: Java 解析器上下文接口, Previous: Java Location Values, Up: Java 解析器 [Contents][Index]
生成的分析器类的名称默认为 。可以使用“”更改前缀。
或者,使用 '' 给出一个
类的自定义名称。下面详细介绍了此类的接口。YYParser
YY
%define api.prefix%define api.parser.class {name}
默认情况下,分析器类具有包可见性。宣言
'' 将更改为公开可见性。记得
根据 Java 语言规范,在这种情况下,文件的名称应与类的名称匹配。
同样,可以使用 和 与声明一起添加
Parser 类的其他修饰符。可以使用单个 '' 指令来添加
对 Parser 类的任意数量的批注。%define api.parser.public.javaapi.parser.abstract
api.parser.final
api.parser.strictfp
%define
%define
api.parser.annotations {annotations}
解析器类的 Java 包名称可以使用
'' 指令。超类和实现的
可以使用 和 '' 指令指定解析器类的接口。%define package%define
api.parser.extends
%define api.parser.implements
解析器类定义一个内部类 ,用于
用于位置跟踪(请参阅 Java 位置值)和内部
接口(请参阅 Java 扫描程序接口)。以外
这些内部类/接口,以及接口中描述的成员
下面,所有其他成员和字段前面都带有 OR 前缀,以避免与用户代码发生冲突。Location
Lexer
yy
YY
可以使用该指令扩展解析器类。该指令的每次出现都会向解析器类添加一个字段,并向其构造函数添加一个参数,
它会自动初始化它们。%parse-param
protected
final
使用嵌入式 . 构建一个新的解析器对象。有
没有参数,除非使用 s 和/或 s 和/或 s。%code lexer
%param
%parse-param
%lex-param
用于添加到构造函数开头的代码
身体。这对于初始化超类特别有用。用
'' 指定任何未捕获的异常。%code init
%define init_throws
Lexer
lexer, parse_param, ...) ¶使用指定的扫描程序生成新的分析器对象。没有
其他参数,除非 S 和/或 S 是
使用。%param
%parse-param
如果扫描程序由 定义,则此构造函数为
声明并使用扫描程序自动调用
使用正确的 S 和/或 S 创建。%code lexer
protected
%param
%lex-param
用于添加到构造函数开头的代码
身体。这对于初始化超类特别有用。用
'' 指定任何未捕获的异常。%code init
%define init_throws
运行句法分析,否则返回成功。true
false
获取或设置用于生成详细错误消息的选项。这些只是 可用于 ''(或 ''), 这也会打开详细的错误消息。%define parse.error detailedverbose
String
msg) ¶Position
pos, String
msg) ¶Location
loc, String
msg) ¶使用扫描仪的方法打印错误消息
实例正在使用中。和 参数为
仅当位置跟踪处于活动状态时才可用。yyerror
Location
Position
java.io.PrintStream
o) ¶获取或设置用于跟踪分析的流。它默认为 。System.err
int
l) ¶获取或设置跟踪级别。目前它的值为 0、无迹、 或非零,完全跟踪。
如果启用了令牌国际化(请参阅令牌国际化),则必须 为解析器提供以下函数:
string
s)¶返回用户语言的翻译。举个例子:s
%code { static ResourceBundle myResources = ResourceBundle.getBundle("domain-name"); static final String i18n(String s) { return myResources.getString(s); } }
下一篇: Java 扫描器接口, 上一篇: Java 解析器接口, 上一篇: Java 解析器 [内容][索引]
解析器上下文提供用于在以下情况下生成错误报告的信息: 调用 ''。%define parse.error custom
所有语法符号、标记和非终端的枚举。其 枚举器是从符号名称伪造的:
public enum SymbolKind { S_YYEOF(0), /* "end of file" */ S_YYERROR(1), /* error */ S_YYUNDEF(2), /* "invalid token" */ S_BANG(3), /* "!" */ S_PLUS(4), /* "+" */ S_MINUS(5), /* "-" */ [...] S_NUM(13), /* "number" */ S_NEG(14), /* NEG */ S_YYACCEPT(15), /* $accept */ S_input(16), /* input */ S_line(17); /* line */ };
这个符号的名称,可能是翻译的。
那种前瞻。返回时没有前瞻。null
前瞻的位置。
YYParser.SymbolKind[]
argv, int
argc) ¶填充预期的标记,其中从不包含 或 。argvSymbolKind.S_YYERROR
SymbolKind.S_YYUNDEF
永远不要把更多的元素放进去,在成功上
返回存储在 中的令牌数。如果还有更多
预期的令牌比 ,填满并返回
0. 如果没有预期的令牌,则也返回 0,但设置为 。argcargvargvargcargvargcargv[0]
null
如果为 null,则返回存储所有可能文件所需的大小
值,它总是小于 。argvYYNTOKENS
下一篇: Java Actions 专用功能, 上一篇: Java 解析器上下文接口, 上一篇: Java 解析器 [内容][索引]
有两种可能的方法可以连接 Bison 生成的 Java 解析器
使用扫描仪:扫描仪可以由 或 定义
在其他地方定义。无论哪种情况,扫描程序都必须实现解析器类的内部接口。此接口还
包含所有用户定义的令牌名称和预定义令牌的常量。%code lexer
Lexer
YYEOF
在第一种情况下,扫描仪类的主体被放置在块中。如果要从
Parser 构造函数添加到 scanner 构造函数中,用 ;它们在 s 之前传递给
构造 函数。%code lexer
%lex-param
%parse-param
在第二种情况下,扫描仪必须实现接口,
在解析器类中定义(例如,)。
然后,解析器对象的构造函数将接受一个对象
实现接口; 不在此使用
箱。Lexer
YYParser.Lexer
%lex-param
在这两种情况下,扫描程序都必须实现以下方法。
Location
loc, String
msg) ¶此方法由用户定义,用于发出错误消息。第一个
如果位置跟踪未处于活动状态,则省略参数。其类型可以是
使用 更改了 。%define api.location.type {class-name}
返回下一个令牌。它的类型是返回值、语义值和 位置由界面中的 THEIR 方法保存和返回。不 仅推送解析器需要。
使用 '' 指定任何未捕获的异常。
默认值为 。%define lex_throwsjava.io.IOException
分别返回返回的最后一个令牌的第一个位置,以及超出它的第一个位置。不需要这些方法
除非位置跟踪和拉取解析处于活动状态。yylex
他们应该为每次调用返回新对象,以避免所有符号 共享相同的位置边界。
可以使用 更改返回类型。%define api.position.type
{class-name}
返回 yylex 返回的最后一个标记的语义值。不需要 用于仅推送解析器。
可以使用 '' 更改返回类型。%define api.value.type {class-name}
YYParser.Context
ctx) ¶如果调用 ''(请参阅 Bison 声明部分),则解析器不再将语法错误消息传递给 ,而是通过调用函数将该任务委托给用户。%define parse.error customyyerror
reportSyntaxError
是否使用取决于用户。yyerror
下面是一个报告函数的示例(请参阅 Java 解析器上下文接口)。
public void reportSyntaxError(YYParser.Context ctx) { System.err.print(ctx.getLocation() + ": syntax error"); // Report the expected tokens. { final int TOKENMAX = 5; YYParser.SymbolKind[] arg = new YYParser.SymbolKind[TOKENMAX]; int n = ctx.getExpectedTokens(arg, TOKENMAX); for (int i = 0; i < n; ++i) System.err.print((i == 0 ? ": expected " : " or ") + arg[i].getName()); } // Report the unexpected token which triggered the error. { YYParser.SymbolKind lookahead = ctx.getToken(); if (lookahead != null) System.err.print(" before " + lookahead.getName()); } System.err.println(""); }
此实现不适合国际化,请参阅示例以获取更好的替代方案。c/bistromathic
下一篇: Java Push Parser 接口, 上一篇: Java Scanner 接口, 上一篇: Java 解析器 [Contents][Index]
以下特殊构造可以在 Java 操作中使用。 其他类似的 C 操作功能目前不适用于 Java。
使用 '' 指定解析器中任何未捕获的异常
操作,以及 指定的初始操作。%define throws%initial-action
当前规则的第 th 个组件的语义值。 这可能不会分配给。 请参阅 Java 语义值。n
喜欢,但指定替代类型。
请参阅 Java 语义值。$n
typealt
当前规则创建的分组的语义值。作为
值,这是基类型(或由
'') 如 not 强制转换为声明的子类型,因为
不允许在 Java 赋值的左侧进行强制转换。
如果需要正确的子类型,请使用显式 Java 强制转换。
请参阅 Java 语义值。Object
%define api.value.type
与 Java 始终允许分配给基类型相同。
也许我们应该使用它来表示值和设置值,但目前没有简单的方法来区分
这些结构。
请参阅 Java 语义值。$$
$<>$
$$
当前规则的第 th 个组件的位置信息。 这可能不会分配给。 请参阅Java 位置值。n
当前规则创建的分组的位置信息。 请参阅Java 位置值。
;
¶立即从解析器返回,指示失败。 请参阅 Java 解析器接口。
;
¶立即从解析器返回,表示成功。 请参阅 Java 解析器接口。
String
msg) ¶位置
loc, 字符串
msg) ¶位置
loc, 字符串
消息) ¶使用扫描仪的方法打印错误消息
实例正在使用中。和 参数为
仅当位置跟踪处于活动状态时才可用。yyerror
Location
Position
下一篇: C/C++ 和 Java 语法的区别, 上一篇: Java 操作中使用的特殊功能, 上一篇: Java 解析器 [内容][索引]]
通常,Bison 会为 Java 生成一个拉取解析器。 以下 Bison 声明说您希望解析器是推送 解析器(参见 %define 摘要):
%define api.push-pull push
关于 Java 拉取解析器接口的大多数讨论(参见 Java 解析器接口)也适用于推送解析器接口。
生成推送解析器时,该方法是使用
以下签名(取决于是否启用了位置)。push_parse
int
token, Object
yylval) ¶int
token, Object
yylval, Location
yyloc) ¶int
token, Object
yylval, Position
yypos) ¶与拉取解析器的主要区别在于解析器
重复调用方法以解析每个令牌。这
如果 '' 或
使用 '' 声明(请参阅 %define 摘要)。和 参数可用
仅当位置跟踪处于活动状态时。push_parse
%define api.push-pull push%define api.push-pull bothLocation
Position
该方法返回的值为下列值之一:
0 (成功)、1 (中止)、2 (内存耗尽) 或 .这
如果需要更多输入,则可以返回新值
完成解析语法。push_parse
YYPUSH_MORE
YYPUSH_MORE
如果定义为 ,则生成的解析器
类也将实现该方法。此方法的主体是
重复调用扫描程序,然后传递获取的值的循环
从扫描仪到方法。api.push-pull
both
parse
push_parse
还有一个额外的并发症。从技术上讲,推送解析器不会
需要了解扫描程序(即实现接口的对象),但它确实需要访问该方法。目前,该方法定义在
接口。因此,实现这一点
为了提供 的实现,仍然需要接口。目前的方法(可能会发生变化)是要求
要为实现接口的对象指定的构造函数。这个对象只需要实现方法;其他方法可以存根,因为它们会
永远不要被调用。最简单的方法是添加一个微不足道的扫描仪
使用所需的任何实现实现到语法文件。下面的代码示例演示了一种简单的方法
完成此操作。YYParser.Lexer
yyerror
yyerror
YYParser.Lexer
yyerror
YYParser
YYParser.Lexer
yyerror
yyerror
%code lexer { public Object getLVal () {return null;} public int yylex () {return 0;} public void yyerror (String s) {System.err.println(s);} }
下一篇: Java 声明摘要, 上一篇: Java 推送解析器接口, 上一篇: Java 解析器 [Contents][索引]]
Java 语言的不同结构迫使存在一些差异 在 C/C++ 语法和为 Java 解析器设计的语法之间。这 部分总结了这些差异。
YYERROR
YYACCEPT
YYABORT
return
请注意,在这三个符号中,只有 和 将导致从方法8 返回。YYACCEPT
YYABORT
yyparse
%union
Object
%define api.value.type%token
type
$n
$$
$$
$n
@n
%code imports
块放在 Java 源代码的开头。他们可能
包括版权声明。对于声明,请使用
'' 代替。package
%define api.package
的 %code
块放置在 Parser 类中。
%code lexer
块(如果指定)应包括 扫描器。如果没有这样的块,扫描仪可以是任何类 实现适当的接口(请参阅 Java 扫描程序接口)。
Java 解析器不支持其他块。
特别是,不应使用块
并可能在 Bison 的未来版本中出现错误。%code
%{ … %}
结语与 C/C++ 代码中的含义相同,它可以 用于定义解析器在解析器类之外使用的其他类。
上一篇: C/C++ 和 Java 语法的区别,上一篇: Java 解析器 [内容][索引]
此摘要仅包括特定于 Java 的声明或具有特殊 在 Java 解析器中使用时的含义。
为解析器生成一个 Java 类。
由 only 定义的词法分析器类的参数,作为参数添加到词法分析器构造函数和解析器中
创建词法分析器的构造函数。默认值为 none。
请参阅 Java 扫描程序接口。%code lexer
作为参数添加到构造函数的解析器类的参数 并作为由构造函数初始化的字段。默认值为 none。 请参阅 Java 解析器接口。
声明令牌。请注意,尖括号括起来的是 Java 类型。 请参阅 Java 语义值。
声明非终端的类型。请注意,尖括号括起来 Java 类型。 请参阅 Java 语义值。
追加到分析器类内部的代码。 请参阅 C/C++ 和 Java 语法之间的差异。
在声明之后插入的代码。
请参阅 C/C++ 和 Java 语法之间的差异。package
在分析器构造函数主体的开头插入的代码。 请参阅 Java 解析器接口。
添加到分析器类内词法分析器类的主体的代码。 请参阅 Java 扫描程序接口。
代码(在第二个之后)追加到文件末尾,在解析器类之外。
请参阅 C/C++ 和 Java 语法之间的差异。%%
不支持。请改用。
请参阅 C/C++ 和 Java 语法之间的差异。%code imports
如果
'' 未使用。默认值为 。
请参阅 Java Bison 接口。prefixParser
%define api.parser.classYY
是否声明解析器类。默认值为 false。
请参阅 Java Bison 接口。abstract
解析器类的 Java 注释。默认值为 none。 请参阅 Java Bison 接口。
分析器类的名称。默认值为 或 。请参阅 Java Bison 接口。YYParser
api.prefixParser
解析器类的超类。默认值为 none。 请参阅 Java Bison 接口。
是否声明解析器类。默认值为 false。
请参阅 Java Bison 接口。final
解析器类的实现接口,一个逗号分隔的列表。 默认值为 none。 请参阅 Java Bison 接口。
是否声明解析器类。默认值为 false。
请参阅 Java Bison 接口。public
是否声明解析器类。默认值为 false。
请参阅 Java Bison 接口。strictfp
从解析器类引发的异常
构造 函数。默认值为 none。
请参阅 Java 解析器接口。%code init
词法分析器方法引发的异常,一个
逗号分隔的列表。默认值为 。
请参阅 Java 扫描程序接口。yylex
java.io.IOException
用于位置的类的名称(介于 2 之间的范围
位置)。此类作为分析器的内部类生成
类 由 .默认值为 。
原名 .
请参阅Java 位置值。bison
Location
location_type
要放入分析器类的包。默认值为 none。
请参阅 Java Bison 接口。
从 Bison 3.7 中重命名。package
用于职位的类的名称。此类必须由
用户。默认值为 。
原名 .
请参阅Java 位置值。Position
position_type
语义值的基本类型。默认值为 。
请参阅 Java 语义值。Object
用户提供的分析器操作引发的异常,以及逗号分隔的列表。默认值为 none。
请参阅 Java 解析器接口。%initial-action
下一篇: Bison 版本兼容性:最佳实践, 上一篇: 用其他语言编写的解析器, 上一篇: Bison [内容][索引]
下一篇: yacchack, Up: A Brief History of the Greater Ungulates [Contents][Index]
Bison 起源于一个名为 Yacc — Yet Another 的程序的类似工作 编译器编译器。9 Yacc 是在贝尔实验室编写的,是早期的一部分 Unix的开发;它的第一个用途是开发原始的 可移植 C 编译器,pcc。同一个人,史蒂文·约翰逊(Steven C. Johnson),写了Yacc和 原始 PCC。
根据作者 10 的说法, Yacc 于 1971 年首次发明,并达到了一种可识别的类似于 1973 年的 C 版本。Johnson 出版了 A Portable Compiler: Theory 和实践(见Johnson 1978)。
Yacc 本身最初不是用 C 语言编写的,而是用它的前身语言编写的, B.这在很大程度上解释了它的奇怪界面,它暴露了大量的 的全局变量,而不是将它们捆绑到 C 结构中。所有其他 类似 Yacc 的程序是从 Yacc 的 C 端口衍生而来的。
Yacc,通过其在 pcc 中的部署和作为 生成其他解析器,帮助推动了 Unix 的早期传播。雅克 然而,在 1990 年左右之后,当 Workalikes 随着许可证限制的减少和更多功能的可用。
当Caldera发布源代码时,原始Yacc已普遍可用 2002 年旧版本的 Unix 高达 V7 和 32V。到那时,它已经 在实际使用中早已被 Bison 取代,甚至在 Yacc 的原生 Unix 上也是如此 变种。
下一篇: Berkeley Yacc, Previous: The ancestral Yacc, Up: A Brief History of the Greater Ungulates [Contents][Index]
原始 Yacc 的缺陷之一是它无法生产 可重入解析器。这首先通过一组插入式补救措施 名为“yacchack”的修改,由Eric S. Raymond在USENET上发布 1983年左右。当 zoo 和 Berkeley Yacc 时,这段代码很快就被遗忘了 几年后可用。
Berkeley Yacc 由 Robert Corbett 于 1985 年创立(参见 Corbett 1984)。它最初被命名为“动物园”,但到 1989 年 10 月它变成了 被称为伯克利 Yacc 或 byacc。
伯克利 Yacc 与祖先的 Yacc 相比有三个优势:它产生了 更快的解析器,它可以生成可重入的解析器,并且源代码是 发布到公共领域,而不是在AT&T的专有下 许可证。更好的性能来自于实现以下技术 DeRemer 和 Penello 关于 LALR 解析的开创性论文(参见 DeRemer 1982)。
由于其公共领域许可,byacc的使用迅速传播。然而,一旦 Bison 变得可用,byacc 本身已经不再普遍使用。
Robert Corbett 实际上在 1985 年编写了两个(密切相关的)LALR 解析器, 两者都使用 DeRemer/Penello 技术。一个是“动物园”,另一个是 “拜森”。1987年,理查德·斯托曼(Richard Stallman)开始研究拜森(Byson);名称已更改 到 Bison,界面变得与 Yacc 兼容。
Yacc 和 Byson/Bison 之间的主要可见区别
Byson 的第一个版本是 Byson 支持该构造
(允许访问起始和结束行号和字符号
与当前规则中的任何符号相关联)。@n
还有命令''说不要提及
如果存在 shift/reduce 冲突且没有 reduce/reduce 冲突,则发生冲突
冲突。在较新版本的 Bison 中,其 reduce/reduce 冲突的变体可以应用于
个人规则。%expect nn%expect
%expect-rr
更高版本的 Bison 添加了更多新功能。
Bison 错误报告已通过各种方式得到改进。特别是。祖先的 Yacc 和 Byson 在错误消息中没有插入符号。
与 Yacc 相比,Bison 使用更快但空间效率更低的编码 解析表(参见 Corbett 1984),以及更现代的技术 生成前瞻集(参见 DeRemer 1982)。这种方法是 从那时起,标准一。
(也有人合理地声称算法的差异 主要来自约翰逊不得不犯下的可怕的 kludges 原来的 Yacc 适合 PDP-11。
命名引用、语义谓词、、%析构函数、转储到 DOT、、、 和转储到 XSLT、LAC 和 IELR(1)
一代是野牛的新世代。%locations
%glr-parser
%printer
%parse-param
%lex-param
Bison 还具有许多支持 C++ 的功能,这些功能在 祖先 Yacc 或 Byson。
Bison 淘汰了所有以前的 Yacc 变体和 workalikes 生成 C 1995.
Bison 提供了一种 Yacc 兼容模式,在该模式下,它努力符合 POSIX 标准。按照 POSIX 标准编写的语法文件,以及 不要利用野牛的任何特殊能力,应该 无需修改即可与许多版本的 Bison 一起使用。
Bison 的所有其他功能都是 Bison 特有的,并且正在发生变化。野牛 积极维护并不断发展。它应该是否定的 令人惊讶的是,旧版本的 Bison 不会接受 Bison 源代码 使用较旧的 Bison 中根本不存在的较新功能。 令人遗憾的是,尽管为保持兼容性做出了合理的努力, 也可能发生相反的情况:可能会发生使用 旧版本的 Bison 不会在没有 修改。
因为 Bison 是一个代码生成工具,所以可以保留其输出 并将其分发给程序的用户。然后用户不是 需要完全安装 Bison,只是实现 编程语言,如C,这是处理生成的 输出。
Bison 的输出旨在具有最大的可移植性。 所以,也就是说,而 Bison 语法源代码可能具有依赖性 在特定版本的 Bison 上,从任何版本的 Bison 生成的解析器 应该与大量的 C 实现一起工作,或者其他什么 语言是适用的。
使用 Bison 的推荐最佳实践(在软件上下文中 以源代码形式分发)是将生成的解析器发送到 下游用户。只有那些积极从事开发的下游用户 需要对语法文件进行更改的程序需要有 Bison 完全安装,这些用户可以安装特定版本的 Bison 这是必需的。
遵循此推荐做法还可以使用更新的 Bison比用户通过操作系统发行版提供的内容, 从而利用 Bison 允许的最新技术。
Bison 的一些特征已经或正在被采用到其他类似 Yacc 的 Yacc 中 程序。因此,编写语法代码似乎是个好主意 它针对多个实现,类似于 C 程序的方式 通常针对多个编译器和语言版本编写。以外 POSIX 描述的 Yacc 子集,Bison 语言并不严格 标准化。当 Bison 功能被另一个解析器生成器采用时,它 最初可能与它所基于的 Bison 版本兼容, 但兼容性可能会在未来下降。努力使 他们的 Bison 代码同时与其他解析器生成器兼容是 尽管如此,我们仍然鼓励使用所有生成器的特定版本,并且仍然 请遵循传送生成输出的建议做法。例如 一个项目可以在内部保持与多个生成器的兼容性, 并选择要交付给用户的特定输出。否则 该项目可以交付所有输出,为用户安排一种方式 指定用于构建程序的程序。
下一篇: 野牛符号, 上一篇: 野牛版本兼容性: 最佳实践, 上一篇: 野牛 [内容][索引]
关于野牛的几个问题偶尔会出现。这里有一些 已解决。
以下现象有几种症状,导致 以下典型问题:
我调用了几次,在正确的输入下它就可以工作了 适当地;但是,当发现解析错误时,所有其他调用都会失败 太。如何重置 的错误标志?
yyparse
yyparse
或
我的解析器包括对类似“”功能的支持,其中 我逃避的案例.尽管我做到了,但这失败了 指定 ''。#include
yyparse
yyparse
%define api.pure full
这些问题通常不是来自野牛本身,而是来自 Lex 生成的扫描仪。因为这些扫描仪使用较大的缓冲区 速度,他们可能不会注意到输入文件的变化。作为 演示,请考虑以下源文件:first-line.l
%{ #include <stdio.h> #include <stdlib.h> %}
%% .*\n ECHO; return 1; %%
int yyparse (char const *file) { yyin = fopen (file, "r"); if (!yyin) { perror ("fopen"); exit (EXIT_FAILURE); }
/* One token only. */ yylex (); if (fclose (yyin) != 0) { perror ("fclose"); exit (EXIT_FAILURE); } return 0; }
int main (void) { yyparse ("input"); yyparse ("input"); return 0; }
如果文件包含input
input:1: Hello, input:2: World!
然后,您得到的不是两次第一行,而是:
$ flex -ofirst-line.c first-line.l $ gcc -ofirst-line first-line.c -ll $ ./first-line input:1: Hello, input:2: World!
因此,每当您更改时,都必须告诉
Lex 生成的扫描程序,用于丢弃其当前缓冲区并切换到
新的。这取决于您对 Lex 的实施;查看其
文档了解更多信息。对于 Flex,调用
'' 在每次更改为 .如果你的
Flex 生成的扫描仪需要从多个输入流读取到
处理包含文件等功能,您可以考虑使用 Flex
像 '' 这样的函数,用于操作多个
输入缓冲区。yyin
YY_FLUSH_BUFFERyyin
yy_switch_to_buffer
如果 Flex 生成的扫描仪使用启动条件(请参阅《Flex 手册》中的启动条件),则可以 还想重置扫描仪的状态,即回到初始 启动条件,通过调用 ''.BEGIN (0)
下一篇: 实现 gotos/loops, 上一篇: 如何重置解析器, 上一篇: 常见问题 [内容][索引]
我的解析器似乎破坏了旧字符串,或者它可能失去了对 他们。它不是报告“”,而是报告 '',甚至''。"foo", "bar""bar", "bar""foo\nbar", "bar"
此错误可能是发送到的最常见的“错误报告”。 野牛列出,但只关心对角色的误解 扫描仪。请考虑以下 Lex 代码:
%{ #include <stdio.h> char *yylval = NULL; %}
%% .* yylval = yytext; return 1; \n continue; %%
int main () { /* Similar to using $1, $2 in a Bison action. */ char *fst = (yylex (), yylval); char *snd = (yylex (), yylval); printf ("\"%s\", \"%s\"\n", fst, snd); return 0; }
如果编译并运行此代码,则会获得:
$ flex -osplit-lines.c split-lines.l $ gcc -osplit-lines split-lines.c -ll $ printf 'one\ntwo\n' | ./split-lines "one two", "two"
这是因为是为在操作中读取而提供的缓冲区,但如果你想保留它,你必须复制它
(例如,使用 )。请注意,输出可能取决于如何
您的 Lex 句柄实现 。例如,当
给定 Lex 兼容性选项(触发
option '') Flex 会生成不同的行为:yytext
strdup
yytext
-l%array
$ flex -l -osplit-lines.c split-lines.l $ gcc -osplit-lines split-lines.c -ll $ printf 'one\ntwo\n' | ./split-lines "two", "two"
我的简单计算器支持变量、赋值和函数, 但是我怎样才能实现 gotos 或循环呢?
虽然非常具有教育意义,但文档中包含的示例模糊不清 解析器之间的区别 - 其工作是恢复 文本的结构,并将其传输到后续模块 程序 - 以及此的处理(例如执行) 结构。这适用于所谓的直线程序, 即,恰恰是那些具有简单执行模型的: 一个接一个地执行简单的指令。
如果你想要一个更丰富的模型,你可能需要使用解析器 构造一棵树来表示它所具有的结构 恢复;这棵树通常被称为抽象语法树, 或简称 AST。然后,穿过这棵树, 以各种方式遍历它,将使诸如 执行或其翻译,这将导致口译员或 编译器。
这个主题远远超出了本手册的范围,读者是 邀请查阅专门的文献。
下一篇: 安全吗?Conform?, Previous: 实现 gotos/loops, up: 常见问题 [目录][索引]
我有几个密切相关的语法,我想分享他们的 实现。事实上,我可以使用单一语法,但可以使用多个语法 入口点。
Bison 不支持多个开始符号,但有一个非常简单的
意味着模拟它们。如果 和 是两个伪
start-symbols,然后引入两个新标记,比如 和 ,并将它们用作实际 start-symbol 的开关:foo
bar
START_FOO
START_BAR
%token START_FOO START_BAR; %start start; start: START_FOO foo | START_BAR bar;
这些令牌可防止引入新的冲突。至于 解析器说,这就是所需要的。
现在困难的部分是确保扫描程序将发送这些令牌
第一。如果您的扫描仪是手写的,那应该很简单。如果
您的扫描仪是由 Lex 生成的,他们有简单的方法可以做到这一点:
回想一下,第一个之后的“”之间的任何内容都是
在生成的函数顶部逐字复制。做
确保扫描仪中有一个变量可用(例如,一个
全局变量或使用等),并使用以下命令:%{ ... %}%%
yylex
start_token
%lex-param
/* Prologue. */ %% %{ if (start_token) { int t = start_token; start_token = 0; return t; } %} /* The rules. */
野牛安全吗?是否符合POSIX?
如果您正在寻找保证或认证,我们不提供。 但是,Bison 旨在成为一个符合 Yacc 的 POSIX 规范。如果您遇到问题,请给我们发送 错误报告。
下一篇: 我无法构建 Bison, 上一篇: 安全吗?符合?,向上:常见问题 [目录][索引]
很长一段时间以来,对于许多 GNU 软件包用户来说,这一直是一个痛苦的问题
包不可重定位。这意味着用户无法复制程序,
由同一台计算机上的其他用户安装到其主目录,
并使其正常工作(包括 i18n)。这么多用户需要去
通过它的所有
依赖项、选项和障碍。configure; make; make install
大多数包管理系统,允许用户安装 预建二进制包,解决了“易用性”的 安装“的问题,但它们硬连线路径名,通常为 或 .这意味着用户需要 root 安装二进制包的权限,并阻止安装两个 同一二进制包的不同版本。/usr/usr/local
可以将可重定位的程序移动或复制到其他位置 在文件系统上。可以对已安装的 并移动程序,并通过符号链接调用它们。是的 只有在硬链接的情况下,才可以使用硬链接做同样的事情 链接文件与真实程序位于同一目录中。
若要将程序配置为可重定位,请添加到命令行。--enable-relocatableconfigure
在某些操作系统上,可执行文件会记住共享库的位置
并且更喜欢它们而不是任何其他搜索路径。因此,这样的
可执行文件将首先在原始库中查找其共享库
安装目录,然后才在当前安装中
目录。因此,为了可靠性,最好还给出一个指向不存在的目录的选项
现在和永远不会被创建,例如.您可以在命令行上使用
避免安装到该目录中。--prefix--prefix=/nonexistentDESTDIR=dest-dir
make
我们不建议使用非特权用户可写入的前缀 (例如),因为这样的目录可以重新创建 由非特权用户在删除原始目录后。 我们也不建议使用可能位于自动挂载程序后面的前缀 (例如)因为性能影响 目录搜索。/tmp/inst$$$HOME/inst$$
下面是一个示例安装运行,其中考虑了所有这些因素 建议:
./configure --enable-relocatable --prefix=/nonexistent make make install DESTDIR=/tmp/inst$$
安装将不起作用 setuid 或 setgid 可执行文件,因为此类可执行文件仅搜索 出于安全原因的系统库路径。--enable-relocatable
运行时损失和大小损失在 GNU/Linux 上可以忽略不计(只是 当可执行文件启动时,一个系统调用更多),并且很小 其他系统(包装程序只是设置一个环境变量 并执行实际程序)。
下一篇: 在哪里可以找到帮助?, 上一篇: 启用可重定位性, 上一篇: 常见问题 [目录][索引]
我无法构建野牛,因为找不到抱怨。 我该怎么办?
make
msgfmt
像大多数支持国际化的 GNU 软件包一样,该特性 默认情况下处于打开状态。如果在子目录中构建时遇到问题,则表明系统的国际化 缺乏支持。您可以重新配置 Bison 以关闭此支持,也可以安装 GNU 从 https://ftp.gnu.org/gnu/gettext/ 获取文本并重新配置 野牛。有关详细信息,请参阅文件。po--disable-nlsABOUT-NLS
我无法构建 Bison,因为我的 C 编译器太旧了。
除了 GLR 解析器(需要 C99),Bison 生成的 C 代码
只需要 C89 或更高版本。然而,野牛本身需要普通的C99
诸如语句后的声明等功能。Bison 的脚本尝试在默认的编译器上启用 C99(或更高版本)支持
到 C99 之前。如果您的编译器完全缺少这些 C99 功能,GCC 可能会
好吧,是一个更好的选择;或者您可以尝试升级到编译器的最新版本
版本。configure
下一篇: Bug 报告, 上一篇: 我无法构建 Bison, 向上: 常见问题 [目录][索引]
我在使用 Bison 时遇到问题。在哪里可以找到帮助?
首先,阅读这本精美的手册。除此之外,您还可以向 help-bison@gnu.org 发送邮件。此邮件列表旨在 挤满了愿意回答有关使用 并安装 Bison。请记住,(大多数)人 名单上有他们生活的方面与野牛无关(! 因此,您可能不会立即收到问题的答案。这可以 令人沮丧,但请尽量不要按喇叭;请记住,任何 他们提供的帮助纯粹是自愿的,是出于他们的善意 红桃。
下一篇: 更多语言, 上一篇: 在哪里可以找到帮助?, 上一篇: 常见问题 [目录][索引]
我发现了一个错误。我应该在错误报告中包含哪些内容?
在发送错误报告之前,请确保您使用的是最新的 版本。检查 https://ftp.gnu.org/pub/gnu/bison/ 或其中之一 镜子。请务必在错误报告中包含版本号。如果 该错误存在于最新版本中,但不存在于以前的版本中, 尝试确定不包含该错误的最新版本。
如果 bug 与解析器相关,则应包含最小的语法 你可以演示这个错误。语法文件也应该是 完成(即,我应该能够通过 Bison 运行它,而无需 编辑或添加任何内容)。语法越小越简单, 修复错误会更容易。
包括有关编译环境的信息,包括
操作系统的名称和版本以及编译器的名称和
版本。如果您在编译时遇到问题,还应该包含一个
生成会话的脚本,从调用 开始。根据错误的性质,您可能会被要求
同时发送其他文件(例如 或 )。configure
config.hconfig.cache
补丁是最受欢迎的,但不是必需的。也就是说,不要犹豫 仅仅因为您无法提供修复程序而发送错误报告。
向 bug-bison@gnu.org 发送错误报告。
Bison 会支持 C++ 和 Java 吗?怎么样 ?insert your favorite language here
支持 C++、D 和 Java。我们很乐意添加其他语言; 欢迎投稿。
成为 beta 测试人员涉及什么?
这并不是很复杂。基本上,您将下载一个测试 发布、编译它,并使用它来构建和运行一两个解析器。后 也就是说,您将提交错误报告或消息 一切都很好。报告成功以及 失败,因为测试版本最终会成为主流版本, 但前提是它们经过充分测试。如果没有人测试,开发是 基本上停止了。
对于 开发人员不容易访问。他们目前可以轻松访问 最新的 GNU/Linux 和 Solaris 版本。有关其他操作的报告 系统尤其受欢迎。
如何加入 help-bison 和 bug-bison 邮件列表?
在操作中,右侧第 -th 符号的位置 的规则。请参阅追踪位置。n
在语法中,Bison 生成的用于中间规则操作的非终端符号 具有语义值。请参阅 Midrule 操作翻译。
在语法中,Bison 生成的用于中间规则操作的非终端符号 没有语义价值。请参阅 Midrule 操作翻译。
用于将语法规则部分与 野牛宣言部分或结语。 请参阅 Bison 语法的整体布局。
“”和“”之间列出的所有代码都是逐字复制的 添加到解析器实现文件。这样的代码构成了 语法文件。参见 Bison 语法大纲。%{%}
谓词操作。这是一种可能出现在 规则。对表达式进行计算,如果为 false,则会导致语法错误。在 非确定性操作期间的GLR解析器, 这无提示地导致替代解析死亡。在确定性期间 操作,与YYERROR的效果相同。 请参阅使用任意谓词控制分析。
将逐字插入到输出解析器源中 默认位置或位于 指定的位置。 请参见%code 摘要。codequalifier
定义一个变量来调整 Bison 的行为。请参阅 %define 摘要。
Bison 声明,用于为解析时使用的规则分配优先级 解决减少/减少冲突的时间。请参阅编写 GLR 解析器。
标记令牌流结束的预定义令牌。它不可能 在语法中使用。
为错误恢复保留的令牌名称。此令牌可用于
语法规则,以便允许 Bison 解析器识别
语法而不停止该过程。实际上,一句话
包含错误可能会被识别为有效。在语法错误时,
token 成为当前的 Lookahead 令牌。行动
对应的 然后执行,并展望
令牌将重置为最初导致冲突的令牌。
请参阅错误恢复。error
error
代表“”的过时指令。%define parse.error verbose
Bison 声明生成 GLR 解析器。请参阅编写 GLR 解析器。
Bison 声明,用于将合并函数分配给规则。如果有 reduce/reduce 与具有相同合并函数的规则冲突, 函数应用于两个语义值以获得单个结果。 请参阅编写 GLR 解析器。
被变量淘汰(请参阅同一程序中的多个解析器)。%define
api.prefix
重命名解析器中使用的外部符号(变量和函数),以便
它们以“而不是”开头。与此相反,不要重命名类型和宏。prefixyyapi.prefix
在 C 解析器中重命名的符号的精确列表是 、 、 、 和 (如果使用位置) 。如果使用
推送解析器、 、 、 和 也将重命名。为
例如,如果使用 '',则名称将变为 、 等。有关 C++ 分析器,请参阅本节中的文档。yyparse
yylex
yyerror
yynerrs
yylval
yychar
yydebug
yylloc
yypush_parse
yypull_parse
yypstate
yypstate_new
yypstate_delete
%name-prefix "c_"c_parse
c_lex
%define api.namespace
Bison 声明指定两者都应该接受的其他参数。请参阅 Parser 函数 yyparse
。yylex
yyparse
Bison 声明指定应接受的其他参数。请参阅 Parser 函数 yyparse
。yyparse
“”的弃用版本(参见 %define Summary),Bison 对此更谨慎地警告 不合理使用。%define api.pure
需要 Bison 的 Vison版本或更高版本。请参阅需要 Bison 版本。version
预定义的标记,返回的所有未定义值都映射到该标记上。它不能在语法中使用,而是使用 .yylex
error
Macro 假装发生了不可恢复的语法错误,立即返回 1。未调用错误报告函数。请参阅 Parser 函数 yyparse
。yyparse
yyerror
对于 Java 解析器,此功能将使用 instead 调用。return YYABORT;
宏来假装语言的完整话语已经
读取,立即返回 0。
请参阅 Parser 函数 yyparse
。yyparse
对于 Java 解析器,此功能将使用 instead 调用。return YYACCEPT;
宏,用于丢弃解析器堆栈中的值并伪造前瞻 令 牌。请参阅在操作中使用的特殊功能。
Bison 的整数版本,例如 3.7.4 版本的 30704。
仅在中定义。在版本 3.7.4 之前,是
定义为 1。yacc.cYYBISON
外部整数变量,包含
Lookahead 令牌。(在纯解析器中,它是 中的局部变量。错误恢复规则操作可能会检查此变量。
请参阅在操作中使用的特殊功能。yyparse
没有前瞻令牌时的伪令牌类型。
表示的令牌类型是输入流的末尾。
立即导致语法错误。此语句引发错误
恢复,就好像解析器本身检测到错误一样;但是,它
不调用 ,也不打印任何消息。如果你
想要打印错误消息,请在之前显式调用
'' 语句。请参阅错误恢复。yyerror
yyerror
YYERROR;
对于 Java 解析器,此功能将使用 instead 调用。return YYERROR;
用户提供的函数,以便在错误时调用。
请参阅错误报告功能 yyerror
。yyparse
用于指定解析器堆栈的初始大小的宏。 请参阅内存管理以及如何避免内存耗尽。
用户提供的词法分析器函数,调用时没有要获取的参数
下一个令牌。请参阅 Lexical Analyzer 函数 yylex
。
应放置行和列的外部变量
与令牌关联的数字。(在纯解析器中,它是局部的
变量,并将其地址传递给 .)
如果您不使用 '' 功能,则可以忽略此变量
语法操作。
请参阅令牌的文本位置。
在语义操作中,它存储 lookahead 令牌的位置。
请参阅操作和位置。yylex
yyparse
yylex
@
应将语义放置在其中的外部变量
与令牌关联的值。(在纯解析器中,它是局部的
变量,并将其地址传递给 .)
请参阅标记的语义值。
在语义操作中,它存储 lookahead 标记的语义值。
请参阅操作。yylex
yyparse
yylex
用于指定解析器堆栈的最大大小的宏。请参阅内存管理以及如何避免内存耗尽。
Bison 每次报告语法错误时递增的全局变量。
(在纯解析器中,它是 中的局部变量。在
纯推送解析器,它是 的成员。
请参阅错误报告功能 yyerror
。yyparse
yypstate
Macro 假装内存耗尽,通过返回 2
马上。调用错误报告函数。
请参阅 Parser 函数 yyparse
。yyparse
yyerror
Bison 生成的解析器函数;调用此函数开始
解析。请参阅 Parser 函数 yyparse
。
删除解析器实例的函数,由 Bison 在推送模式下生成;
调用此函数可删除与解析器关联的内存。
请参见yypstate_delete
。调用时不执行任何操作
使用空指针。
创建解析器实例的函数,由 Bison 在推送模式下生成;
调用此函数以创建新的解析器。
请参见yypstate_new
。
Bison 在推送模式下产生的解析器函数;调用此函数
分析输入流的其余部分。
请参见yypull_parse
。
Bison 在推送模式下产生的解析器函数;调用此函数
解析单个令牌。
请参见yypush_parse
。
当解析器生成 1 时,表达式生成 1
正在从语法错误中恢复,否则为 0。
请参阅在操作中使用的特殊功能。YYRECOVERING ()
用于控制当
C 语言中的确定性解析器需要扩展其堆栈。如果定义为 0,
解析器将用于扩展其堆栈和内存耗尽
如果失败,则发生(请参阅内存管理和如何避免内存耗尽)。如果定义为
1、解析器会使用 .除 0 和 1 之外的值为
保留用于将来的 Bison 扩展。如果未定义,则默认为 0。alloca
malloc
malloc
alloca
YYSTACK_USE_ALLOCA
在非常常见的情况下,您的代码可能在具有
堆栈有限且堆栈溢出检查不可靠,您应该
设置为不可能导致
调用时,任何目标主机上的未检查堆栈溢出。您可以检查 Bison 的代码
生成以确定正确的数值。这将
需要一些低级实现细节方面的专业知识。YYMAXDEPTH
alloca
语法的所有符号、标记和非终结符的枚举。
请参阅语法错误报告功能yyreport_syntax_error
。使用符号种类
内部由解析器,不应与令牌类型混淆:
终端符号的符号类型不等于其代币类型!(除非
'' 被使用。%define api.token.raw
表示未知令牌的令牌类型。
下一篇: GNU自由文档许可证, 上一篇: 野牛符号, 上一篇: 野牛 [目录][索引]
其唯一操作是接受操作的状态。 因此,接受状态是一致的状态。 请参阅了解解析器。
最初提出的指定上下文无关语法的形式化方法 作者:John Backus,Peter Naur 在他的 1960-01-02 中略有改进 为后来的 Algol 60 报告做出贡献的委员会文件。 请参阅语言和上下文无关语法。
仅包含一个可能操作的状态。请参阅默认减少。
指定为规则的语法,无论上下文如何都可以应用。 因此,如果有一条规则说整数可以用作 表达式,表达式所在的任何地方都允许使用整数 允许。请参阅语言和上下文无关语法。
标记和/或非终端的序列,带有一个点,表示 冲突。该点标记发生冲突的位置。
一个统一的反例是具有两个不同 解析;它的存在证明了语法是模棱两可的。当一个统一 在合理的时间内找不到反例,构建了一个非统一的反例:两个不同的字符串共享前缀 up 到点。
请参阅反例的生成
如果当前解析器状态,则解析器应执行的减少 不包含 Lookahead 令牌的其他操作。在允许的解析器中 州,Bison宣布减少,最大的前瞻将是 默认减少并删除该展望集。请参阅默认减少。
具有默认减少的一致状态。请参阅默认减少。
在执行期间发生的内存分配,而不是在 编译时或输入函数时。
类似于集合论中的空集合,空字符串是 长度为零的字符串。
具有离散状态的“机器”,据说它存在于 每一刻都在时间里。当机器的输入被处理时, 机器从一个状态移动到另一个状态,由逻辑指定 机器。在解析器的情况下,输入是语言 解析后,状态对应于语法中的各个阶段 规则。请参阅 Bison 解析器算法。
一种解析算法,可以处理所有与上下文无关的语法,包括那些 不是 LR(1)。它解决了野牛的情况 确定性解析 算法无法通过有效地拆分多个解析器,尝试所有 可能的解析器,并根据其他解析器丢弃那些失败的解析器 正确的上下文。请参阅广义 LR (GLR) 解析。
(通常)在语法上可分割的语言结构; 例如,C 语言中的“expression”或“declaration”。 请参阅语言和上下文无关语法。
最小 LR(1) 解析器表构造算法。也就是说,给定任何 上下文无关语法,IELR(1) 生成具有完整 规范 LR(1) 的语言识别能力,但几乎相同 解析器状态数为 LALR(1)。这种解析器状态的减少是 通常是一个数量级。更重要的是,因为规范的 LR(1) 在非 LR(1) 的情况下,额外的解析器状态可能包含重复的冲突 语法中,IELR(1) 的冲突数量通常是一个数量级 也更少。这可以大大降低开发 语法。请参阅 LR 表构造。
一个算术运算符,它位于操作数之间 执行一些操作。
设备或程序之间的连续数据流。
“Token”和“symbol”都重载为表示语法符号 (kind) 或与事件关联的所有解析信息(kind、value、location) 输入中的语法符号。为了消除歧义,
token_kind_t
token_kind_type
TokenKind
总而言之:当你看到“种类”时,将“符号”或“标记”解释为表示 语法符号。当你看不到“善良”时(包括当你 请参阅“type”),将“symbol”或“token”解释为表示解析的 符号。
一种解决延迟语法错误问题的解析机制
检测,这是由 LR 状态合并、默认值减少和
的使用。延迟的语法错误检测导致
意外的语义操作,在错误中启动错误恢复
句法上下文,以及详细的预期标记列表不正确
语法错误消息。请参见 LAC。%nonassoc
该语言的典型用法模式之一。例如,其中一个
C 语言的结构是语句。
请参阅语言和上下文无关语法。if
从左到右分析具有左关联性的运算符: '' 首先计算 '',然后结合 ‘’.请参阅运算符优先级。a+b+ca+bc
其结果符号也是其第一个组件符号的规则;为 示例,''。请参阅递归规则。expseq1 : expseq1 ',' exp;
解析一种语言的句子,通过逐个分析它 从左到右。请参阅 Bison 解析器算法。
读取输入流并逐个返回令牌的函数。
请参阅 Lexical Analyzer 函数 yylex
。
一个标志,由语法规则中的操作设置,它改变了方式 对令牌进行解析。请参阅 Lexical Tie-ins。
由两个或多个固定字符组成的标记。请参阅符号、终端和非终端。
已读取但尚未移动的令牌。请参阅 Lookahead 令牌。
Bison(像大多数其他解析器一样)的上下文无关语法类 generators)默认可以处理;LR(1) 的子集。 见神秘的冲突。
上下文无关语法类,其中最多一个标记 需要 Lookahead 来消除任何输入的解析的歧义。
一个语法符号,代表可以 通过规则以较小的结构表示;在其他 words,一种不是标记的结构。请参阅符号、终端和非终端。
通过分析来识别语言的有效句子的函数 从词法传递给它的一组标记的语法结构 分析器。
一个算术运算符,放在它所基于的操作数之后 执行一些操作。
将一串非终端和/或终端替换为单个 非终结,根据语法规则。请参阅 Bison 解析器算法。
可重入子程序是一个子程序,可以在任何 并联次数,各种之间无干扰 调用。请参阅纯(重入)解析器。
一种语言,其中所有运算符都是后缀运算符。
其结果符号也是其最后一个组件符号的规则;为 示例,''。请参阅递归规则。expseq1: exp ',' expseq1;
在计算机语言中,语义由操作指定 针对语言的每个实例,即 每个语句。请参阅定义语言语义。
据说解析器在选择分析时会发生变化 来自流的进一步输入,而不是立即减少一些 已经公认的规则。请参阅 Bison 解析器算法。
按原样识别和解释的单个字符。 请参阅从正式规则到 Bison 输入。
代表完整有效话语的非终端符号 正在分析的语言。起始符号通常列为 语言规范中的第一个非终端符号。 请参阅开始符号。
语法符号的(有限)枚举,由解析器处理。 请参阅符号、终端和非终端。
一种数据结构,其中符号名称和关联数据存储在
解析以允许重复识别和使用现有信息
符号的用法。请参阅多功能计算器:mfcalc
。
解析输入流时遇到的错误,由于无效 语法。请参阅错误恢复。
语法中没有规则的语法符号,因此是 语法不可分割。它所代表的一段文本是一个标记。 请参阅语言和上下文无关语法。
一种语言的基本、语法上不可分割的单位。符号 描述语法中的标记是终端符号。的输入 Bison 解析器是来自词法分析器的标记流。 请参阅符号、终端和非终端。
语法终端的(有限)枚举,由 扫描器。请参阅符号、终端和非终端。
不存在从中转换到的一系列的解析器状态 解析器的开始状态。在冲突期间,一个国家可能变得无法到达 分辨率。请参阅无法访问的状态。
Copyright © 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc. https://fsf.org/ Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
本许可证的目的是制作手册、教科书或其他 在自由的意义上,功能性和实用性文档是自由的:到 确保每个人都有复制和重新分发它的有效自由, 无论是否对其进行修改,无论是商业性的还是非商业性的。 其次,本许可证为作者和出版商保留了一种方式 为他们的工作获得荣誉,同时不被视为负责任 对于其他人所做的修改。
该许可证是一种“copyleft”,这意味着衍生 文件作品本身必须是同样意义上的自由。它 补充了 GNU 通用公共许可证,这是一个 copyleft 专为自由软件设计的许可证。
我们设计了此许可证,以便免费将其用于手册 软件,因为自由软件需要自由文档:一个自由的 程序应附带手册,提供与 软件可以。但本许可证不仅限于软件手册; 它可以用于任何文本作品,无论主题或 是否以印刷书籍的形式出版。我们推荐此许可证 主要用于以指导或参考为目的的作品。
本许可证适用于任何媒介中的任何手动或其他作品, 包含版权所有者放置的通知,说明它可以 根据本许可证的条款分发。此类通知授予 全球范围内的免版税许可,期限不受限制,可以使用它 在此规定的条件下工作。下面的“文件”, 指任何此类手册或作品。任何公众成员都是 被许可人,并称呼为“您”。如果您符合以下条件,即表示您接受许可证 以需要许可的方式复制、修改或分发作品 根据版权法。
文档的“修改版本”是指包含 文档或其部分,可以逐字复制,也可以使用 修改和/或翻译成另一种语言。
“次要部分”是命名的附录或前言部分 专门处理 文档的发布者或作者对文档的整体 主题(或相关事项),并且不包含任何可能落下的内容 直接在那个整体主题中。(因此,如果文档在 一部分是数学教科书,中学部分可能无法解释 任何数学。这种关系可能是一个历史问题 与主题或相关事项或法律的联系, 商业、哲学、道德或政治立场 他们。
“不变部分”是某些次要部分,其标题 在通知中被指定为固定部分 表示本文档是根据本许可证发布的。如果 部分不符合上述中学的定义,则不是 允许指定为不变。文档可能包含零 不变部分。如果文档未标识任何不变性 部分,然后就没有了。
“封面文本”是列出的某些短文本段落, 作为封面文本或封底文本,在通知中说 本文档根据本许可发布。封面文字可能 最多 5 个字,封底文字最多 25 个字。
文档的“透明”副本是指机器可读的副本, 以规范可用于 适合修改文件的公众 直接使用通用文本编辑器或(对于由 像素)通用的绘画程序或(用于图纸)一些广泛可用的 绘图编辑器,适用于输入文本格式化程序或 用于自动转换为适合输入的各种格式 文本格式化程序。在其他透明文件中制作的副本 其标记或没有标记的格式已被安排为阻止 或劝阻读者的后续修改是不透明的。 如果大量使用图像格式,则图像格式不透明 的文本。不“透明”的副本称为“不透明”。
适合透明副本的格式示例包括普通格式 无标记的 ASCII、Texinfo 输入格式、LaTeX 输入 格式、SGML 或 XML 使用公开可用的 DTD,以及符合标准的简单 HTML, 专为人工修改而设计的 PostScript 或 PDF。例子 透明图像格式包括 PNG、XCF 和 JPG。不透明格式包括专有格式,这些格式可以是 仅由专有文字处理器、SGML 或 DTD 和/或处理工具的 XML 不普遍可用,以及机器生成的 HTML, PostScript 或 PDF 由某些文字处理器生成 仅用于输出目的。
“扉页”是指,对于印刷书籍,扉页本身, 加上以下页面,以清晰地保存材料 此许可证要求出现在标题页中。对于作品 没有任何标题页的格式,因此,“标题页”是指 作品标题最显眼的外观附近的文字, 在正文开头之前。
“出版商”是指分发副本的任何个人或实体 向公众公布该文件。
“标题为 XYZ”的部分是指文档的命名子单元,其 title 要么精确为 XYZ,要么在括号中包含 XYZ 将 XYZ 翻译成另一种语言的文本。(这里 XYZ 代表 下面提到的特定部分名称,例如“致谢”, “奉献”、“认可”或“历史”。“保留标题” 当您修改文档时,这意味着它仍然是 根据此定义,“标题为 XYZ”部分。
该文件可能在通知旁边包含保修免责声明,该声明 声明本许可证适用于文档。这些保修 免责声明被视为通过引用包含在本文件中 许可,但仅限于免责保证:任何其他 这些保修免责声明可能具有的暗示是无效的,并且具有 对本许可证的含义没有影响。
您可以在任何媒体上复制和分发文档 商业或非商业,前提是本许可、 版权声明,以及说明本许可证适用的许可声明 到文档的所有副本中复制,并且您不添加其他 本许可的任何条件。您不得使用 阻碍或控制读数或进一步阅读的技术措施 复制您制作或分发的副本。但是,您可以接受 补偿以换取副本。如果你分发一个足够大的 份数 您还必须遵守第 3 节中的条件。
您也可以在上述相同条件下出借副本,并且 您可以公开展示副本。
如果您发布印刷副本(或通常具有 打印的封面)的文件,编号超过 100 个,以及 文档的许可声明需要封面文本,您必须附上 封面副本,清晰易读地包含所有这些封面 文本:封面上的封面文字和封底上的文字 封底。两个封面也必须清晰易读地识别 您作为这些副本的发布者。前盖必须存在 标题中所有单词均同样突出的完整标题,并且 可见。此外,您还可以在封面上添加其他材料。 复印时更改仅限于封面,只要它们保留 文档的标题并满足这些条件,可以处理 作为其他方面的逐字复制。
如果任一封面所需的文本太长而无法容纳 清晰地,您应该列出第一个(尽可能多的 合理地)在实际封面上,其余部分继续到相邻的 页面。
如果发布或分发文档编号的不透明副本 超过 100 个,则必须包含机器可读的透明 与每个不透明副本一起复制,或在每个不透明副本中或与每个不透明副本一起声明 一般网络使用的计算机网络位置 公众可以使用公共标准网络协议进行下载 文档的完整透明副本,不添加任何材料。 如果您使用后一种选择,则必须采取合理谨慎的步骤, 当您开始大量分发不透明副本时,以确保 因此,此透明副本将保留在所述位置的可访问性 位置,直到您上次分发 不透明的副本(直接或通过您的代理商或零售商) 向公众发布版本。
要求(但不是必需)您联系 在重新分发任何大量副本之前,请做好文档记录,以提供 他们有机会为您提供文档的更新版本。
您可以在以下位置复制和分发文档的修改版本 上述第 2 节和第 3 节的条件,前提是您释放 正是本许可证下的修改版本,以及修改后的 版本填充文档的角色,从而许可分发 以及将修改版本修改给拥有副本的人 的。此外,您必须在修改版本中执行以下操作:
如果修改后的版本包含新的前言部分,或者 符合次要部分条件且不包含任何材料的附录 从文档中复制,您可以选择指定部分或全部 这些部分为不变。为此,请将其标题添加到 修改版本许可声明中的固定部分列表。 这些标题必须与任何其他部分标题不同。
您可以添加标题为“背书”的部分,前提是它包含 只不过是各种人对您的修改版本的认可 当事方 - 例如,同行评议的声明或文本具有 被组织批准为权威定义 标准。
您可以添加最多五个单词的段落作为封面文本,以及 最多 25 个单词作为封底文本段落,到列表末尾 修改版的封面文本。只有一段 封面文字和封底文字之一可以通过(或 通过)任何一个实体的安排。如果文档已经 包含您之前添加的同一封面的封面文本,或者 根据您代表的同一实体作出的安排, 您不得添加其他;但您可以明确替换旧的 来自添加旧发布者的上一个发布者的权限。
文档的作者和发布者不遵守本许可 允许使用他们的名字进行宣传或主张或 暗示对任何修改版本的认可。
您可以将本文档与根据本文档发布的其他文档合并 许可,根据上文第 4 节中定义的条款进行修改 版本,前提是您在组合中包含所有 所有原始文档的固定部分,未修改,以及 将它们全部列为组合工作的不变部分,在其 许可声明,并且您保留其所有保修免责声明。
合并的作品只需包含本许可证的一份副本,并且 多个相同的不变部分可以替换为单个 复制。如果有多个同名的固定部分,但 不同的内容,使每个此类部分的标题具有唯一性 在末尾的括号中添加原文的名称 该部分的作者或出版商(如果已知),或者是唯一编号。 对列表中的章节标题进行相同的调整 合并作品的许可通知中的固定部分。
在组合中,您必须合并标题为“历史记录”的任何部分 在各种原始文件中,形成一个标题为 “历史”;同样,将标题为“致谢”的任何部分合并, 以及标题为“奉献”的任何部分。您必须删除所有 标题为“背书”的部分。
您可以收集由文档和其他文档组成的集合 根据本许可证发布,并替换本许可证的单个副本 使用包含在 集合,前提是您遵守本许可证的规则 在所有其他方面逐字复印每份文件。
您可以从此类集合中提取单个文档,然后分发 它根据本许可证单独使用,前提是您插入此许可证的副本 许可进入提取的文档,并遵循本许可证 关于逐字复制该文件的其他方面。
本文件或其衍生物与其他单独文件的汇编 以及独立的文件或作品,在存储卷中或存储卷上,或 分发介质,称为“聚合”,如果版权 编译产生的不用于限制合法权利 汇编的用户超出了个人作品的允许范围。 当文档包含在聚合中时,本许可证不会 适用于其他非本身的作品 本文件的衍生作品。
如果第 3 节的封面文本要求适用于这些 文档的副本,则如果文档少于 整个聚合,文档的封面文本可以放在 涵盖将文档括在聚合中的括号,或 如果文件是电子形式的,则相当于封面的电子等价物。 否则,它们必须出现在括起整个封面的印刷封面上 骨料。
翻译被认为是一种修改,所以你可以 根据第 4 节的条款分发文档的翻译。 用翻译替换固定部分需要特殊的 获得其版权所有者的许可,但您可以包括 部分或全部不变部分的翻译,以及 这些固定部分的原始版本。您可以包括 本许可证的翻译,以及 文件和任何保修免责声明,前提是您还包括 本许可证的英文原文和原始版本 这些通知和免责声明。如果两者之间存在分歧 本许可证或声明的翻译和原始版本 或免责声明,以原始版本为准。
如果文档中的某个部分标题为“致谢”, “奉献”或“历史”,要求(第 4 节)保存 其标题(第 1 节)通常需要更改实际 标题。
您不得复制、修改、再许可或分发文档 除非本许可证明确规定。任何尝试 否则,复制、修改、再许可或分发它是无效的,并且 将自动终止您在本许可项下的权利。
但是,如果您停止所有违反本许可证的行为,则您的许可证 (a)暂时恢复特定版权所有者的版权, 除非并且直到版权所有者明确并最终 终止您的许可,并且 (b) 永久终止,如果版权所有者 未能在以下情况下通过某种合理方式通知您违规行为 戒烟后 60 天。
此外,您从特定版权所有者处获得的许可是 如果版权所有者通知您 通过一些合理的手段违规,这是你第一次有 收到违反本许可(任何作品)的通知 版权所有者,并且您在 30 天后纠正违规行为 您收到通知。
终止您在本节项下的权利并不终止 根据 本许可证。如果您的权利已被终止,而不是永久终止 恢复,收到部分或全部相同材料的副本 不授予您任何使用它的权利。
自由软件基金会可能会发布新的修订版本 不时获得 GNU 自由文档许可证。这样的新 版本在精神上将与当前版本相似,但可能 在细节上有所不同,以解决新的问题或疑虑。请参见 https://www.gnu.org/licenses/。
许可证的每个版本都有一个可区分的版本号。 如果文档指定了此文档的特定编号版本 许可证“或任何更高版本”适用于它,您可以选择 遵循该指定版本的条款和条件,或 已发布的任何更高版本(非草稿) 自由软件基金会。如果文档未指定版本 本许可证的编号,您可以选择任何曾经发布过的版本(不是 作为草案)由自由软件基金会(Free Software Foundation)提供。如果文档 指定代理可以决定将来的哪个版本 可以使用许可证,该代理的公开声明接受 version 永久授权您为 公文。
“大规模多作者协作网站”(或“MMC 网站”)是指任何 万维网服务器,发布受版权保护的作品,也 为任何人编辑这些作品提供了突出的设施。一个 任何人都可以编辑的公共 wiki 就是这种服务器的一个例子。一个 “大规模多作者协作”(或“MMC”)包含在 网站是指在MMC上发布的任何一组受版权保护的作品 网站。
“CC-BY-SA”是指知识共享署名-相同方式共享 3.0 由非营利性组织 Creative Commons Corporation 发布的许可证 主要营业地点在旧金山的公司, 加利福尼亚州,以及该许可证的未来 copyleft 版本 由同一组织发布。
“合并”是指发布或重新发布整个文档或 部分,作为另一份文件的一部分。
如果 MMC 根据此获得许可,则“有资格获得许可” 许可,以及是否在此许可下首次发布的所有作品 在此 MMC 以外的地方,随后合并为整体 或部分进入 MMC,(1) 没有封面文本或固定部分, (2)在2008年11月1日之前注册成立。
MMC 站点的运营商可以重新发布站点中包含的 MMC 在 2009 年 8 月 1 日之前的任何时间在同一网站上的 CC-BY-SA 下, 前提是 MMC 有资格获得许可。
要在您编写的文档中使用本许可证,请附上 许可证在文档中,并放置以下版权和 扉页后面的许可证声明:
Copyright (C) year your name. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled ``GNU Free Documentation License''.
如果您有固定章节、封面文本和封底文本, 将“替换为...文本“行与此相呼应:
with the Invariant Sections being list their titles, with the Front-Cover Texts being list, and with the Back-Cover Texts being list.
如果您有没有封面文本的固定部分,或其他一些 将这三者结合起来,合并这两个备选方案以适应 情况。
如果您的文档包含重要的程序代码示例,我们 建议在您选择的 自由软件许可证,如GNU通用公共许可证, 允许它们在自由软件中使用。
下一篇: 术语索引, 上一篇: GNU 自由文档许可证, 上一篇: Bison [内容][索引]
罗伯特·保罗·科贝特, 编译器错误恢复中的静态语义 博士论文,报告编号UCB/CSD 85/251, 电气工程与计算机科学系,计算机科学系 加州大学伯克利分校分部 (1985年6月)。https://digicoll.lib.berkeley.edu/record/135875
Joel E. Denny 和 Brian A. Malloy,IELR(1):实用 LR(1) 解析器表 对于非 LR(1) 语法与冲突解决,在 2008 ACM Symposium on Applied Computing (SAC'08), ACM, New York, NY, USA, 第240-245页。https://dx.doi.org/10.1145/1363686.1363747
Joel E. Denny, PSLR(1): 伪无扫描仪最小 LR(1) Deterministic Parsing of Composite Languages, 博士论文, Clemson 美国南卡罗来纳州克莱姆森大学(2010年5月)。https://tigerprints.clemson.edu/all_dissertations/519/
Joel E. Denny 和 Brian A. Malloy,用于生成的 IELR(1) 算法 用于非 LR(1) 语法的最小 LR(1) 解析表,并解决冲突, in Science of Computer Programming,第 75 卷,第 11 期(11 月 2010),第943-979页。https://dx.doi.org/10.1016/j.scico.2009.08.001
Frank DeRemer 和 Thomas Pennello,LALR 的高效计算(1) Look-Ahead Sets, in ACM Transactions on Programming Languages 和 《系统》,第 4 卷,第 4 期(1982 年 10 月),第 1982 页。 615–649.https://dx.doi.org/10.1145/69622.357187
奇纳瓦特·伊斯拉迪赛库尔、安德鲁·迈尔斯、 从解析冲突中寻找反例, 在第 36 届 ACM SIGPLAN 会议论文集 编程语言设计与实现 (PLDI '15), ACM,第 555-564 页。https://www.cs.cornell.edu/andru/papers/cupex/cupex.pdf
史蒂文·约翰逊, 可移植编译器:理论与实践, 在第五届 ACM SIGACT-SIGPLAN 研讨会论文集 编程语言原理 (POPL '78), 第97-104页。https://dx.doi.org/10.1145/512760.512771。
Donald E. Knuth, On the Translation of Languages from Left to Right, in Information and Control, Vol. 8, Issue 6 (December 1965), pp. 607–639.https://dx.doi.org/10.1016/S0019-9958(65)90426-2
伊丽莎白·斯科特(Elizabeth Scott),阿德里安·约翰斯通(Adrian Johnstone)和沙姆萨·萨达夫·侯赛因(Shamsa Sadaf Hussain),富田风格的广义LR解析器,皇家霍洛威学院,皇家霍洛威大学 伦敦,计算机科学系,TR-00-12(2000 年 12 月)。https://www.cs.rhul.ac.uk/research/languages/publications/tomita_style_1.ps
跳转到: | $
%
/
: ; < @ | A B C D E F G H I K L M N O P Q R S T U V W X Y Z |
---|
跳转到: | $
%
/
: ; < @ | A B C D E F G H I K L M N O P Q R S T U V W X Y Z |
---|
此示例的扩展版本的来源包括 在 C 中作为 提供,在 C++ 中作为 提供。examples/c/glrexamples/c++/glr
的来源为 。rpcalc
examples/c/rpcalc
一个类似的例子,但使用明确的语法 比 precedence 和 associativity 注释,可用作 。examples/c/calc
的来源是
可用作 .mfcalc
examples/c/mfcalc
但是,通过 C 宏定义不是
推荐的方式。查看位置的数据类型YYLTYPE
请参见 https://austingroupbugs.net/view.php?id=1388#c5220。
此示例的源代码为 。examples/c++/simple.yy
Java 解析器将操作包含在单独的
方法,而不是为了有一个直观的语法
对应于这些 C 宏。yyparse
由于首字母缩略词,名称有时是 给出“YACC”,但约翰逊在描述性论文中使用了“Yacc” 包含在版本中 7 Unix 手册。